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.

The one-line version: 500 excellent, consistent, well-formatted examples beat 50,000 scraped, noisy, contradictory ones. Every time. No hyperparameter searches exist that rescue a bad dataset.

How much data do you actually need?

GoalRealistic volumeNotes
Teaching a strict output format or house tone100–500 examplesSmall 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 examplesThe sweet spot for most production fine-tunes. Diminishing returns set in fairly early on consistent tasks.
Robust general behaviour change across many input types5,000–50,000 examplesCoverage becomes the bottleneck, not raw count.
Preference tuning (DPO / ORPO)1,000–10,000 pairsPairs are more informative per row than SFT examples, so you need fewer.
Continued pretraining / domain adaptationHundreds of MB to GB of raw textA 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.

The consistency trap: if 20% of your examples format dates as 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.

Rule: never hand-write turn markers. Always go through the model's own template function, which reads them from the tokenizer config. In Unsloth that's 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.

πŸͺ™ Token angle: distillation is where the economics of fine-tuning actually pay off. Generating 5,000 teacher responses costs a handful of dollars in API tokens β€” a one-time spend β€” and can replace millions of frontier-model tokens per month at inference. This is the single most common way fine-tuning produces a genuinely large cost reduction rather than an incremental one.

The pre-training data checklist

CheckWhy it matters
Every example is valid JSON on its own lineParsers 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 examplesInconsistency teaches inconsistency. Enforce with a script, not discipline.
Exact duplicates removedDuplicates silently weight the model toward whatever was duplicated and inflate the appearance of learning.
Near-duplicates removedParaphrase clusters skew the distribution. MinHash/LSH or embedding clustering, then keep one per cluster.
Eval set decontaminatedIf a test question appears in training, your eval score is fiction. Grep for overlap before you believe any number.
Task coverage is balanced and intentionalIf 90% of examples are one sub-task, you have built a one-task model. Count your categories.
Output length distribution is realisticTrain only on 2,000-token answers and the model will pad everything to 2,000 tokens.
No PII, secrets, or credentialsWeights are not a secure store and cannot be selectively redacted after the fact.
Refusals and edge cases includedA 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.
What "done with data" feels like: you can open a random line and it is exactly what you would want the model to say. You can name the five categories you're teaching and roughly how many examples each has. A colleague looking at two random lines couldn't tell which came from the train file. If any of those are false, keep working on the data β€” training will not fix it.

πŸ“Ί Watch:

🧠 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?

Further Reading