§16.14

Prepare a Dataset for Fine-Tuning

Build chat-format JSONL for TRL SFTTrainer: messages vs prompt-completion, load_dataset, train/test split, and a chat-template check.

by published 09 Nov 2026 checked against docs 09 Nov 2026 4 min in Hugging Face Markdown

On this page4 sections
  1. Pick one layout
  2. Build, save, load, split
  3. Check the rendered text
  4. Quality checklist

The shape of your dataset decides what a fine-tune learns. TRL’s SFTTrainer takes language-modeling or prompt-completion data, in standard (plain text) or conversational form. For chat models, use the conversational layout: each row holds a list of {"role", "content"} messages. Hand the trainer a conversational dataset and it applies the chat template for you, so your job is clean rows, not tokenization. The exact formats are in the TRL dataset formats docs.

Pick one layout

  • messages: a full conversation in one column. Loss covers the whole sequence unless you set assistant_only_loss=True in SFTConfig.
  • prompt + completion: both are message lists. Loss is computed on the completion only by default.
  • text: a plain string, for non-chat data.

Keep one layout across the whole file.

Build, save, load, split

Write one JSON object per line. The datasets docs call this the most efficient JSON layout, and load_dataset("json", data_files=...) reads it directly. With no split names given, everything lands in train, so split it yourself.

import json
from datasets import load_dataset
from transformers import AutoTokenizer

SYSTEM = "Answer in one short sentence."
pairs = [
    ("What color is the sky?", "The sky is blue."),
    ("What color is grass?", "Grass is green."),
    ("What color is snow?", "Snow is white."),
    ("What color is a ripe banana?", "A ripe banana is yellow."),
]

with open("train.jsonl", "w", encoding="utf-8") as f:
    for q, a in pairs:
        row = {"messages": [
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": q},
            {"role": "assistant", "content": a},
        ]}
        f.write(json.dumps(row, ensure_ascii=False) + "\n")

ds = load_dataset("json", data_files="train.jsonl", split="train")
splits = ds.train_test_split(test_size=0.25, seed=42)  # DatasetDict: "train", "test"

tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
print(tok.apply_chat_template(
    splits["train"][0]["messages"], tokenize=False, add_generation_prompt=False
))

Each line in train.jsonl looks like this:

{"messages": [{"role": "system", "content": "Answer in one short sentence."}, {"role": "user", "content": "What color is the sky?"}, {"role": "assistant", "content": "The sky is blue."}]}

Check the rendered text

The preview uses tokenize=False and add_generation_prompt=False. The chat templating docs say to turn the generation prompt off for training, since the extra assistant-start tokens don’t help there. You print it only to eyeball roles and turn boundaries; SFTTrainer does the same step internally. If you render to a text column yourself, drop messages and tokenize with add_special_tokens=False, because templates already include special tokens.

Then pass both splits in:

from trl import SFTConfig, SFTTrainer

trainer = SFTTrainer(
    model="Qwen/Qwen3-0.6B",
    args=SFTConfig(output_dir="out", eval_strategy="steps", eval_steps=50),
    train_dataset=splits["train"],
    eval_dataset=splits["test"],
)
trainer.train()

Quality checklist

  • Dedupe. Repeated or near-identical rows get memorized and overweight one pattern.
  • One output shape. If answers are one sentence, make every answer one sentence, with the same system prompt, tone and formatting.
  • Enough examples. Rule of thumb, not a documented figure: a few hundred clean rows can teach a format or style. Teaching new facts is usually a job for retrieval; see fine-tuning vs RAG vs prompting.
  • Hold out a test split, and keep it clean. Split before any augmentation, and keep every eval question and its near-duplicates out of train.

Once the rows are right, train with LoRA or QLoRA. To filter or stream large files first, see load, stream, filter; to share the result, upload it.


Next: LoRA fine-tune a small model, QLoRA on a consumer GPU, Upload a model or dataset

Questions

Questions people ask

Do I need to apply the chat template myself before SFTTrainer?
No. When SFTTrainer receives a conversational dataset it applies the chat template automatically. Apply it by hand only to inspect the output, or if you deliberately build a plain text column.
Should I use messages or prompt and completion?
Both are valid for SFTTrainer. With prompt and completion, loss is computed on the completion only by default. With messages, loss covers the full sequence unless you set assistant_only_loss=True in SFTConfig.

← All Hugging Face plates · Search all guides

↑↓ move↵ openalt+↵ copy first command

Keyboard

⌘/ctrl+K or /
Search all guides
alt+↵
In search: copy the guide's first command
j / k
Move through a list of guides
c
On a guide: copy its first command
t
Toggle light / dark
?
This list