Data Is the Product
Lesson 4: Data Is the Product
Here is the uncomfortable truth of this field: the choice between Unsloth and Azure, between rank 16 and rank 64, between 2 epochs and 3, is a rounding error next to the quality of your dataset. Practitioners who do this professionally spend the overwhelming majority of their time on data and a small fraction on training. Budget your own time accordingly.
How much data do you actually need?
| Goal | Realistic volume | Notes |
|---|---|---|
| Teaching a strict output format or house tone | 100β500 examples | Small datasets work here because the task is narrow and the signal is consistent. 200 well-formed JSON examples will reliably change output shape. |
| A single narrow task (classify, extract, transform) | 500β5,000 examples | The sweet spot for most production fine-tunes. Diminishing returns set in fairly early on consistent tasks. |
| Robust general behaviour change across many input types | 5,000β50,000 examples | Coverage becomes the bottleneck, not raw count. |
| Preference tuning (DPO / ORPO) | 1,000β10,000 pairs | Pairs are more informative per row than SFT examples, so you need fewer. |
| Continued pretraining / domain adaptation | Hundreds of MB to GB of raw text | A completely different regime. Token counts in the hundreds of millions, not example counts in the thousands. |
The most cited evidence that quality dominates quantity is Meta's LIMA paper (2023), which fine-tuned a 65B model on just 1,000 carefully curated promptβresponse pairs and found it competitive with models tuned on far larger datasets. The lesson is not "1,000 is always enough" β it's that a small, high-quality, diverse set teaches behaviour effectively, and that scale cannot compensate for inconsistency.
2026-09-17 and 80% as September 17, 2026, you have not taught a formatting rule β you have taught the model that both are acceptable, and it will pick essentially at random. Decide the format once, enforce it programmatically across the whole dataset, and put it in your eval set.
The formats you'll actually write
Everything is JSONL: one JSON object per line, no trailing commas, no wrapping array. Four shapes cover almost all real work.
1. Chat / messages β the default for both Unsloth and Azure
{"messages": [{"role": "system", "content": "You are a claims adjudicator. Respond only with valid JSON."},
{"role": "user", "content": "Claim: rear-ended at a stoplight, police report filed."},
{"role": "assistant", "content": "{\"decision\":\"approve\",\"payout_usd\":4200,\"reason\":\"no-fault collision with report\"}"}]}
{"messages": [{"role": "system", "content": "You are a claims adjudicator. Respond only with valid JSON."},
{"role": "user", "content": "Claim: scratched the door in my own driveway, no third party."},
{"role": "assistant", "content": "{\"decision\":\"deny\",\"payout_usd\":0,\"reason\":\"single-vehicle, not a covered collision\"}"}]}
This is the lingua franca. Azure AI Foundry consumes it, and Unsloth's chat-template helpers consume it. If you write your dataset in this shape, you can move it between a free Colab GPU and a managed cloud job without touching a line of code.
2. Alpaca / instruction β common in older open-source datasets
{"instruction": "Extract the total from this invoice.", "input": "Invoice total: $1,240.00 due 2026-10-01", "output": "1240.00"}
{"instruction": "Summarise this ticket in one sentence.", "input": "Customer says login fails after password reset...", "output": "User cannot log in after resetting their password."}
3. Plain completion β for non-chat tasks
{"prompt": "Translate to French: the invoice is overdue", "completion": " la facture est en retard"}
4. Preference pairs β for DPO / ORPO
{"prompt": "Explain our refund policy to an angry customer.",
"chosen": "I understand the frustration. Here's exactly what we can do...",
"rejected": "Per our policy, refunds are not available. Please refer to the terms."}
The chat template β the silent killer
Every model family expects its own special tokens around turns. Llama uses <|start_header_id|> markers, Qwen uses ChatML (<|im_start|>), Mistral uses [INST]. If you train on one template and infer with another, you have taught the model on structure it will never see again β and the symptom is maddening: a model that seems almost trained, that produces the right content wrapped in the wrong scaffolding, or that ignores instructions at inference despite perfect training loss.
get_chat_template(tokenizer, chat_template="llama-3.1"); in plain Transformers it's tokenizer.apply_chat_template(conversation, tokenize=False). Then apply the same template at inference time β which the merged model usually handles for you, but the unmerged adapter path may not.
Making data when you have none: distillation
You almost certainly don't have thousands of hand-labelled examples, and you don't need to. The standard modern workflow is:
1. Collect 500-2,000 REAL inputs from your product
(support tickets, user prompts, log samples) β no labels needed.
2. Have a strong teacher model (a frontier API model) produce the
ideal output for each input, with a carefully engineered prompt
describing exactly the behaviour you want.
3. Human-review a sample β fix, filter, or reject. This is where
the quality comes from, and it is not optional.
4. SFT a small open model on those pairs.
5. Evaluate the student against the teacher on a held-out set.
Aim for "close enough, 100x cheaper", not "identical".
Techniques for scaling this up include Self-Instruct and Evol-Instruct (generate prompts, then evolve them into harder variants), and tooling like distilabel which orchestrates teacher-model generation with filtering pipelines. Quality control still lands on you: teacher models hallucinate confidently, drift in format, and occasionally refuse. A distillation dataset that hasn't been sampled and reviewed is a dataset of your teacher's bugs.
The pre-training data checklist
| Check | Why it matters |
|---|---|
| Every example is valid JSON on its own line | Parsers fail the whole file, not the bad line. Validate before you upload β this is the #1 cause of failed cloud jobs. |
| Format is identical across examples | Inconsistency teaches inconsistency. Enforce with a script, not discipline. |
| Exact duplicates removed | Duplicates silently weight the model toward whatever was duplicated and inflate the appearance of learning. |
| Near-duplicates removed | Paraphrase clusters skew the distribution. MinHash/LSH or embedding clustering, then keep one per cluster. |
| Eval set decontaminated | If a test question appears in training, your eval score is fiction. Grep for overlap before you believe any number. |
| Task coverage is balanced and intentional | If 90% of examples are one sub-task, you have built a one-task model. Count your categories. |
| Output length distribution is realistic | Train only on 2,000-token answers and the model will pad everything to 2,000 tokens. |
| No PII, secrets, or credentials | Weights are not a secure store and cannot be selectively redacted after the fact. |
| Refusals and edge cases included | A model that has never seen a "this isn't something I should do" example will invent an answer instead. |
Splitting: 80/10/10, and don't cheat
train.jsonl 80% the model learns from this
valid.jsonl 10% watched during training to detect overfitting
test.jsonl 10% touched ONCE, at the end, to report an honest number
Rules:
- Split before you deduplicate, then dedupe each split, so duplicates
cannot straddle the boundary.
- Test must never inform training decisions. If you tune hyperparameters
against it, it has silently become a validation set.
- 200-500 test examples is plenty. Hand-review them; these are your
ground truth.
πΊ Watch:
- LLM Fine-Tuning Explained: The Complete Guide β covers dataset construction and the chat-template problem in the middle third.
- Get Started with Unsloth Studio: Generate Data & Fine-Tune LLMs Locally β NVIDIA Developer. Shows the synthetic data-generation workflow this lesson describes.
- How to finetune LLMs on custom data domains (CPT tutorial with Unsloth) β the continued-pretraining branch, where data volume looks completely different.
π§ Knowledge Check
1. You have 300 support tickets but no labelled outputs. What's the standard, highest-ROI way to build a dataset?
2. Your fine-tune shows beautiful falling training loss but the deployed model ignores instructions. What's the most likely culprit?
3. Half your examples format the date as ISO and half as long-form English. What will the model learn?