How to Check a Fine-Tuning Dataset Before You Train
Quick Answer / TL;DR
Most fine-tunes that go wrong go wrong because of the data. Before spending compute, it is worth checking the dataset for a handful of specific failures: examples that do not fit your chat template, responses that get silently truncated, a loss that trains on the wrong tokens, a missing stop token that keeps the model from learning to stop, and duplicates or eval leakage that inflate your numbers. None of these need a GPU to find, and each one removes a failure that otherwise shows up only after training.
For this cookbook, we consider instruction or chat tuning, where each example is a short conversation that ends in the assistant turn you want the model to learn. Two parts of every example matter for what follows: the prompt, meaning the system and user messages plus the assistant header, and the response, meaning the assistant's reply. When the loss is masked to the completion, the response is the only part the model is actually trained on, so most of the checks below are really about protecting that part.
For clarity, we are assuming your dataset looks like the standard list of message dictionaries before it hits the tokenizer:
[
{"role": "system", "content": "You are a helpful database routing agent."},
{"role": "user", "content": "Fetch the latest logs for user 492."},
{"role": "assistant", "content": "{\"method\": \"query_db\", \"params\": {\"id\": 492}}"}
]Schema and empty turns
Start with structure, before anything is tokenized. Each example should be a list of messages with roles your template understands, and it should end on an assistant turn that has real content. Most chat templates also expect roles to alternate. The case that slips through most often is an empty or whitespace-only response: it passes a schema validator, tokenizes without error, and trains the model on nothing.
Length and truncation
Tokenize each example and compare its length to the max_seq_len you will train with. If your trainer truncates long examples, which is the common default, it usually truncates from the right, so the end of the sequence is what gets cut. Since the response sits at the end, a long example loses its response first. The worst version is silent: when the prompt alone is already longer than max_seq_len, the entire response is removed and the example trains on nothing while still counting toward your step budget. Look at the length distribution, and separately count how many examples lose part or all of their response. If your trainer instead truncates from the left, the prompt is cut and the response survives, which is a different problem worth knowing about.
Loss masking
In instruction tuning you usually mask the prompt and compute the loss only on the response, so the model learns to produce answers rather than to reproduce questions. Training on the whole sequence is also done, but completion-only masking is the common choice and the one this check assumes. If you mask, confirm that every example still has a non-empty response to train on after tokenization and truncation. A response that masks down to zero tokens, because the split was built wrong or because truncation ate it, contributes no signal, and a dataset with many such examples will train slowly or not at all with no error to tell you why.
Stop tokens
A model learns to stop generating only if it is trained to emit the end-of-turn token, the special token your template uses to close an assistant turn. Chat templates append it for you, so in the normal path it is present. It goes missing in two ways: truncation removes it from a long example, or a hand-built labeling pipeline forgets to include it in the supervised target. The symptom at inference is a model that keeps generating past where it should stop. Confirm the terminator is present in the response part of every example.
Duplicates and leakage
Two cheap problems to rule out. Exact duplicates inside the training set over-weight whatever they repeat, pulling the model toward those examples for no reason you chose. Duplicates shared between train and eval are worse: they make your eval score look better than the model deserves, because it is being tested on something it was trained on. Exact matches are easy to find by hashing the text; near-duplicates, such as light paraphrases, need MinHash or embedding similarity, but the exact and cross-split cases are the ones to check first.
Coverage
Look at what the data actually contains, in the proportions you intend, because a model reflects the distribution it was trained on. If one task type, response length, or label dominates by accident, the model will lean that way. Two views are usually enough: the distribution of response lengths, and the distribution of whatever category field you have, such as task, source, or label. Skew you did not plan for is skew you will have to debug later.
Before you train
One last step that is not a dataset check but belongs next to them: run the base model on your eval before you fine-tune. If it already handles the task, fine-tuning may not be the answer, and if it half handles it, the gap shows you what the dataset should concentrate on. Teaching a model something it already knew is a common and expensive way to gain nothing.
None of these checks need a GPU or a training run; tokenizing a dataset and counting is quick and runs on a laptop. Each one removes a failure that otherwise surfaces only after you have paid to train, so the time spent here is cheap insurance.
Common questions
Why is my fine-tuned model worse than the base model?
Often it is the data: responses truncated past the context window, the loss trained on the wrong tokens, or duplicates and eval leakage flattering the numbers. It can also be the training itself, such as too many epochs or too high a learning rate, which pushes the model to overfit the new data and lose general ability. Check the dataset first, since it is faster, then the training configuration.
Why doesn't my fine-tuned model stop generating?
Because it was never trained to emit the end-of-turn token. The template normally adds it, but truncation can remove it from long examples and hand-built pipelines often drop it, so the model never learns where a turn ends. Check that the terminator is present in the response part of every example.
How do I check a fine-tuning dataset for quality?
Before training, check the data and its tokenized form for a handful of specific failures: every example fits your chat template, nothing is silently truncated, the loss is masked to the response, the response ends with a stop token, and duplicates or eval leakage are not inflating your results. All of it runs on a laptop, without a GPU.
Related Cookbooks
Power Analysis for Benchmark Design | SR Cookbooks
Calculate statistical power and minimum detectable accuracy gaps for LLM benchmarks using Python to avoid reporting sampling noise as signal.
Deduplication at Scale: MinHash & LSH in Python | SR Cookbooks
Implement MinHash and Locality-Sensitive Hashing (LSH) in pure Python to eliminate the quadratic bottleneck of near-duplicate detection for ML dataset curation.
Catastrophic Forgetting in Fine-Tuning | SR Cookbooks
What catastrophic forgetting is, why it happens when you fine-tune an LLM, what it costs you, and how to spot it, plus when a specialized small model can safely ignore it.