← All posts

SFT datasets: designing a supervised fine-tuning corpus

A pillar on instruction–response corpora for SFT: pair contracts, coverage, refusals, synthetic vs expert sources, pre-train quality gates, and leakage with evaluation sets.

SFT datasets: designing a supervised fine-tuning corpus
Contents

The team spends a week tuning Low-Rank Adaptation (LoRA) on a local checkpoint. The “dataset” is six hundred chat exports: mixed system prompts, answers without a shared template, not a single explicit refusal, no “stale policy” slice. On a holdout carved from the same chat, the metric rises. In the pilot a lawyer catches a confident answer against a withdrawn document version, and an operator gets a tone that must never ship.

The failure is almost never the optimizer. The failure is treating a Supervised Fine-Tuning (SFT) corpus as a folder of examples instead of a product with a contract, coverage map, and quality gate. This longread is about designing that SFT corpus: what belongs in an instruction→response pair, where examples come from, how not to mix training with evaluation, and when synthetic data helps versus hurts. It continues the dataset engineering overview and complements when fine-tuning is actually needed (the go/no-go decision), without duplicating the Qwen classification case (a narrow experiment) or the general LLM training stack.

Key takeaways

SFT teaches behavior on input→desired-output pairs. It is not raw-web pretraining and not preference data where “answer A beats B.” Mixing families yields pretty loss curves and strange production behavior.

Contract the pair before you chase volume. Output format, language, when to refuse, when to escalate to a human—fixed before labeling and before synthesis.

Coverage beats size. A thousand similar “short polite answers” lose to a few hundred stratified cases with refusals, edge classes, and real user wording.

Synthetic data drafts; golden editors sign. Domain style and facts are approved by a person with a rubric.

Before training—a static gate: schema, deduplication, zero overlap with the eval set, refusal share, chat-template consistency.

Evaluation must not live in the same file as training. Otherwise you fit prompt and data to the metric instead of testing the system. The broader frame is in evaluating LLMs before production.

How an SFT corpus differs from neighboring families

One project often hosts four families. For SFT it is critical not to confuse their jobs.

Family Teaches / measures Typical unit Mixing failure
SFT corpus How to answer (format, tone, pattern) instruction → response Training on the eval golden without an audit
Preference data Which of two plausible answers to prefer chosen / rejected Style without facts; “liked” instead of “correct”
Golden set Measure behavior input + acceptance criteria Fitting the prompt to visible cases
Mining buffer Raw production failures trace without a final label Scoring on unlabeled cases

An SFT corpus may be wider and noisier than a golden set: the goal is to teach a pattern, not to freeze a regression gate. The Continuous Integration (CI) golden is smaller and stricter. Full taxonomy lives in the series pillar; here we expand only the training loop.

In short: if a row cannot be described as “given this input, we want this output (or refusal),” it is not ready for SFT—it is a note or a mining-buffer candidate.

Start with the instruction → output contract

Before labeling, freeze the contract in one place (dataset meta.yaml or a short RFC):

Element Question Example for a policy assistant
Input What does the model see? User question; optionally retrieved chunks
Output Format? Short answer + clause citation; or structured JSON
Refusal When not to answer? No source; outside the Access Control List (ACL); withdrawn version
Escalation When to a human? High risk class; conflict between two documents
Language en / ru / mixed? Answer in the query language
Tone Formal / operator? No slang; no confidence without a source
Ground truth What counts as fact? Current document version in the DMS, not chat

The contract is the rubric’s backbone. Without it two experts write two “correct” answers of different length and structure, and the model learns the average of noise. Inter-annotator agreement falls not because the task is “hard,” but because rules are missing.

On whether fine-tuning is needed at all: if search plus a hard response template already closes the contract, check when fine-tuning is justified first. SFT pays off when you need stable behavior (format, refusal, tone, domain jargon) that prompting and RAG do not hold reliably.

Record schema and chat template

A minimal instruction-tuning record is not “a blob of text,” but fields that survive checkpoint and trainer library changes.

{
  "id": "sft-reg-0142",
  "messages": [
    {"role": "system", "content": "…one system-instruction version…"},
    {"role": "user", "content": "May PPE be stored without an issue log?"},
    {"role": "assistant", "content": "Per policy v3.2 §4.1 — no. …"}
  ],
  "meta": {
    "domain": "ot_pb",
    "intent": "policy_qa",
    "doc_version": "3.2",
    "language": "en",
    "refusal": false,
    "source": "editor",
    "difficulty": "medium"
  }
}

Rules that save weeks of debugging:

  1. One system-instruction version per dataset manifest (or an explicit system_version field). Mixing five prompts in one JSONL trains the model on a policy conflict.
  2. id stays stable across corpus versions: editing an answer must not spawn a “new” case with no trail.
  3. Metadata for slices: domain, intent, document version, language, refusal, source (editor / synthetic / production). Without them you cannot say which slice failed.
  4. Chat template matches production. If production has no system, do not train on a long system that will never be sent at call time.

Classification and extraction may use flatter fields (input / label / spans); the principle is the same: schema + metadata + manifest version.

Sources: experts, production, synthetic, rewrite

Four typical sources—each with its own risk.

Source Upside Risk How to use
Golden editors Domain facts and tone Costly, slow Coverage core and all high-risk refusals
Production logs Real wording PII, ACL, noise, stale answers After filter and review; never treat the model’s reply as truth
Synthetic (LLM) Volume and variants Hallucinations, style monoculture Draft → mandatory review; quota “synthetic ≤ N%”
Rewrite / transcription Org’s gold tone Narrow coverage Good for format; weak as the only intent source

A pattern that works in enterprise pilots: 30–40% core written or approved by an expert; periphery expanded with synthetic data on intent templates; tail fed from the failure buffer after review. Synthetic without a quota and rubric fills the corpus with a “confident average”—the model becomes polite and empty.

Do not confuse “a model generated a pair” with “the pair is verified.” In domains with real failure cost (industrial safety, finance, medicine-adjacent) the final row status comes only after a human. The golden-editor role gets a dedicated satellite later; the rule here is enough: synthetic data does not sign the manifest.

Volume, coverage, and diversity

“How many examples?” without a coverage map is meaningless. Practice ranges (not dogma):

Task type Working corpus start Look here before volume
Classification / routing 1k–20k labels Class balance, confusable classes
Instruction / dialogue SFT 500–5k pairs Intents, refusals, length, language
Structured output (JSON) 300–3k Schema adherence, rare fields
Domain code / SQL 500–8k Dialects, DB schemas, dangerous queries

“More” helps only when intent and condition coverage grows—not the count of paraphrases of one scenario. A coverage map is intents × conditions (source present / absent; current / withdrawn version; one document / conflict). An empty cell matters more than another hundred similar “success” answers.

User-wording diversity is a separate axis. If every user turn is literary prose from one editor, the model transfers poorly to conversational production. Mix short fragmentary queries, typos (if production has them), and formal mail—in shares that mirror traffic, not the annotator’s taste.

For narrow classification on a small model, hundreds of clean examples can suffice—as in the Qwen 0.6B fine-tuning case. For open policy dialogue without refusals and slices, even ten thousand pairs will not save the pilot.

Refusals, escalations, and “plausible but wrong” answers

A corpus without refusals teaches the model to always answer. In an enterprise assistant that is a direct path to invented policy clauses.

Minimum “negative” skills in SFT:

  1. Refusal without a source — “the provided chunks have no answer; I will not invent one.”
  2. ACL refusal — no rights to the document; do not paraphrase from “general knowledge.”
  3. Withdrawn-version refusal — explicitly state the document is out of force.
  4. Escalation — conflict between two norms; hand off to a specialist via template.
  5. Out-of-scope refusal — weather, jokes, if the product forbids them.

Such cases often need 15–30% share at pilot start—otherwise the positive class crushes behavior. Exact share depends on domain risk; what matters is every refusal type from the contract, not a magic percentage.

“Plausible but wrong” answers usually do not go into assistant for SFT (that is closer to preference / DPO territory). For SFT you want a hard-correct output and separate rows where the correct output is a refusal. If you need to choose between two acceptable phrasings, open a preference family separately—otherwise one corpus pulls the model in conflicting directions.

Static checks and a pre-training quality gate

Training must not start from “a folder someone dropped on disk.” Minimum gate:

flowchart TB
  sources[Sources: editor / prod / synthetic] --> normalize[Normalize schema and template]
  normalize --> lint[Static checks]
  lint --> split[Train / dev / holdout]
  split --> trainJob[SFT job]
  split --> evalGate[Eval on golden set]
  evalGate --> release{Regression gate}
  release -->|pass| ship[Ship adapter]
  release -->|fail| buffer[Corpus fix buffer]
  buffer --> sources

Checks that catch most pain before the GPU:

  • Schema: required fields, types, allowed role values.
  • One system per manifest (or explicit versions).
  • Deduplication on normalized user text and near-duplicate embeddings.
  • Overlap with the eval golden — zero shared id and zero near-duplicate questions.
  • Refusal share inside a corridor; empty corridor → alert.
  • Length: truncations, empty content, gigantic log pastes.
  • PII / secrets: tokens, phones, internal URLs—per project policy.
  • Language: language label matches audited text samples.

The gate’s output is a manifest: corpus version, file hashes, per-slice counts, approver, rubric link. Without a manifest, comparing “model A vs B” is meaningless—you are also comparing different data folders.

Separating evaluation: leakage and overfitting

A classic trap: take the “last 10% of the file” as test. If the file is sorted by time or author, the test does not mirror production. Worse—edit train answers while peeking at the same phrasings used in the customer report.

Practical rules:

  1. Holdout — tens to hundreds of cases the team does not open while iterating prompt and corpus. “Ready for pilot” reports use only holdout or a separate golden.
  2. Visible dev set — for debugging; its metrics are not sold to the business as final.
  3. Eval goldens version separately from the train manifest. Cross-references are fine (“train v0.4 evaluated on gold v0.3”); do not stash the golden inside train JSONL “for convenience.”
  4. Paraphrase is leakage too. “May PPE be stored without an issue log?” and “Is an PPE issue log mandatory?” must not sit one in train and one in holdout without an explicit policy. Hunt near-duplicates.

RAG evaluation goldens are built differently—see RAG golden datasets. Boundary: a RAG golden checks retrieval and grounding; an SFT corpus teaches phrasing and answer policy. Shared questions across those loops are leakage candidates if one loop peeks into the other.

Enterprise example: policy assistant

Context: an internal assistant answers employee questions on current policies. RAG already returns chunks; answers still drift in format and sometimes invent clauses missing from the chunks.

Contract: answer only from provided chunks; require doc_id and version; refuse if absent; formal tone.

Corpus v0.2 (example mix):

Slice Share Approver
Success with citation 55% Editor + sampled audit
Refusal: not in chunks 15% Editor
Refusal: withdrawn document 10% Editor
Two-norm conflict → escalate 10% Legal / compliance
Out of product scope 5% Editor
Short conversational query 5% Logs after filter

Synthetic data only paraphrased “success” and “not in chunks” rows—25% quota and mandatory review. After two pilot weeks, failures of “confident answer on empty retrieval” entered the buffer as refusals in v0.3—not as new successful fantasies.

Release metric for the adapter: not average “similar to gold,” but contract-violation rate on holdout (invented clause, missing version, answer on empty context) below threshold.

Industrial example: operator instructions

Context: a kiosk or tablet on the shop floor; the model helps an operator find the instruction step and allowed deviations. Mistakes cost more than in an office chat.

Corpus differences:

  • Short turns, speech-recognition noise, mixed language (local language + equipment codes).
  • Hard refusal on “how to bypass the interlock” and on off-shift / off-clearance requests.
  • Rich metadata: line, machine type, tech-card version, clearance level.

Here SFT volume is often smaller than marketing expects: 800–1,500 approved pairs with full refusal and line coverage beat five thousand synthetic “polite” dialogues. The link to real forms and domain gap (lab set ≠ field) is the same logic as in the handwritten-digit OCR piece: resemblance to operations first, scale second.

The narrow “question classification → label” experiment shows that a small model + clean labels can beat a large checkpoint with a dirty set. Do not port that win one-to-one to open dialogue: the learning unit is not a class but an answer policy, and the cost of “almost right” text is higher.

Reading map:

Question Article
Is fine-tuning needed? When fine-tuning is needed
How do data families work? Dataset engineering
How to measure before prod? Evaluate LLMs before production
Golden for RAG only RAG golden dataset
Classification case Fine-tuning Qwen 0.6B

This piece answers: how to design the corpus once “we will fine-tune” is decided.

Common mistakes

A chat folder with no schema. Mixed systems, mixed formats, no id—zero reproducibility.

Zero refusals. The model learns to be helpful always; in production that is confident hallucination.

90% synthetic with no review. Pretty loss charts, empty pilot.

Training on the eval golden. Metrics cheer until the first unfamiliar query.

One author for all user turns. Gap vs real employee or operator language.

Mixing SFT and preferences in one file. The model cannot tell whether to learn “the only correct answer” or “what people tend to like.”

No manifest. You cannot roll back the corpus or explain a regression.

Ignoring ACL and PII in logs. Legal and security debt in the same JSONL as training.

What to try today

  1. Write a one-page pair contract: input, output, refusal, escalation, tone.
  2. Draft an intent × condition coverage map and mark empty cells—that is the labeling queue, not “another LoRA run.”
  3. Add to the draft corpus at least five refusals of each type from the contract.
  4. Run the static gate (schema, dedup, eval overlap) before the first training job.
  5. Name an SFT manifest owner—without whose sign-off the file does not enter the job.

FAQ

How many examples do I need for SFT?

Enough to close the coverage map and refusal corridor—not “whatever that blog used.” Narrow classification often needs hundreds to a couple thousand clean labels; policy dialogue usually needs hundreds to a few thousand stratified pairs. Prefer 700 approved over 7,000 synthetic without an editor.

Can the whole corpus be synthetic?

As a draft and for paraphrase expansion—yes, with a quota and review. As the only ground truth in a high-cost domain—no. Synthetic accelerates; the expert signs.

Does every row need a system prompt?

You need consistency with production. If production uses a fixed system, keep one version per manifest or duplicate the same field deliberately. Five conflicting systems in one corpus teach the model to ignore policy.

How does an SFT corpus differ from a CI golden?

A CI golden is small, stable, hard-rubric—you measure with it. An SFT corpus teaches and may be wider; it changes more often. Releasing only on a random train subsample is a methodology error.

When move from SFT to preference data?

When format and facts mostly hold, but you need a stable choice between two acceptable answers (shorter / fuller, with citation / without). That is a separate family and a separate series article; do not mix chosen/rejected pairs with a single assistant in one JSONL without a policy.

How do I keep personal data from logs out of the corpus?

Filter before labeling, mask per policy, ban raw traces in git without a scanner, sample audits. An unreviwed production row is not “fast SFT”—it is leakage into the training set.

Next in the series

  • Dataset engineering overview — families and lifecycle frame.
  • Golden editors — rubric and adjudication (golden-editors-annotation-workflow-2026).
  • Train / eval / holdout and leakage (dataset-splits-leakage-2026).
  • Preference data: DPO and pairs (preference-dataset-rlhf-2026).
  • Synthetic and hybrid pipelines (synthetic-human-dataset-pipeline-2026).