Skip to content

Supervised Fine-Tuning

Two constructed continuations of the same prompt

Prompt
Write a haiku about the sea.
Possible base continuation
Write a haiku about the sky. Write a haiku about the hills.
Demonstrated response
Waves break on the shore / Silver fish turn in the deep / Salt wind fills the sails

SFT trains on demonstrations of the desired response. A base model may also answer the prompt; the example illustrates competing continuation patterns.

A base language model can answer a question, continue a list of questions, or imitate a document containing the question. Supervised fine-tuning makes demonstrated responses more likely for the inputs we care about.

The architecture and next-token objective can stay the same. What changes is the training distribution: examples now show the desired task, format, and response. A common recipe also concentrates loss on assistant responses. Understanding that mask requires separating the tokens used as context from the tokens used as prediction targets.

Prerequisites

This post assumes you've read:


The problem SFT solves

Pretraining supplies broad patterns and knowledge; SFT adapts the model using labeled demonstrations. Those demonstrations can teach formatting and conversational behavior, but they can also add task-specific information or capabilities.

For example, a dataset might teach an assistant to answer a question, ask for a missing detail, decline a particular request, or emit a structured tool call. All of these can be supervised targets. The useful question is whether the examples cover the behavior you want on unseen inputs.


The data format

A simple SFT example is a pair: an instruction (what the user asks) and a response (what the model should produce). In the rawest possible form, that looks like two strings in a JSON record:

{
  "instruction": "Explain photosynthesis to a 10-year-old in one sentence.",
  "response": "Plants use sunlight to turn water and carbon dioxide into sugar, releasing oxygen."
}

But this isn't what the model sees during training. The model sees one flat sequence of tokens. So we need to wrap the pair in some kind of template that marks where the user's turn ends and the assistant's turn begins. These wrappers go by names like "chat templates" or "instruction formats," and every model family has its own.

Instruction pair → training string
raw record
instruction
Explain photosynthesis to a 10-year-old in one sentence.
response
Plants use sunlight to turn water and carbon dioxide into sugar, releasing oxygen.
The dataset has just these two fields. The template on the right is what the model actually sees.
wrapped training string · Alpaca (Stanford, 2023)
maskedloss applies
Below is an instruction that describes a task. Write a response that appropriately completes the request.⏎ ⏎ ### Instruction:⏎ Explain photosynthesis to a 10-year-old in one sentence.⏎ ⏎ ### Response:⏎ Plants use sunlight to turn water and carbon dioxide into sugar, releasing oxygen.
The gray (masked) tokens are fed into the model as context but contribute zero to the loss. Response targets supply the loss; gradients can still flow through prompt representations.

The same example in three illustrative templates. The response-only mask marks local prediction targets; prompt representations can still receive gradients through the response loss.

The Alpaca format from the original Stanford release uses human-readable section headers. ChatML uses <|im_start|> and <|im_end|> as explicit boundary tokens. Llama 3 uses more structured markers with a <|eot_id|> at the end of each turn. The underlying pair is identical in all three. What differs is the marker vocabulary.

Use the model’s intended chat template during training and serving. Role boundaries, end-of-turn markers, and the assistant generation prefix affect the input distribution. Mismatches can degrade behavior. See special tokens and chat templates.


Choosing which targets contribute to loss

Ordinary causal language-model training scores valid next-token targets, excluding padding and any deliberately masked boundaries. SFT uses the same prediction mechanism.

In response-only SFT, the full conversation is input context, while assistant-response tokens supply the scored targets. Full-sequence SFT, which also scores prompt tokens, is another valid training choice; masking is not the definition of SFT.

Formally, if the full sequence is x1,x2,…,xTx_1, x_2, \ldots, x_T and the response tokens live at positions in the set RR, the SFT loss is:

L=−1∣R∣∑t∈Rlog⁡pθ(xt∣x<t)L = -\frac{1}{|R|}\sum_{t \in R} \log p_\theta(x_t \mid x_{<t})

Compare this to the standard language modeling loss where the sum is over all positions. The only difference is the set we sum over.

One response-only SFT example. Template markers and token boundaries are schematic; loss values are illustrative. Every position participates in the forward pass. Only the five response targets contribute to this objective.

System
<|s|>
masked
system
masked
You
masked
are
masked
helpful.
masked
User
<|u|>
masked
user
masked
What
masked
is
masked
2+2?
masked
Assistant
<|a|>
masked
assistant
masked
The
1.8 nats
answer
0.9 nats
is
0.4 nats
4.
0.6 nats
<|eot|>
0.3 nats

Mean response loss = 4.0 / 5 = 0.80 nats.

Each labeled loss scores that target from the preceding context. Masking a target loss does not prevent its input representation from receiving gradients through later predictions. Other SFT recipes also train on prompt tokens.

Response-only loss allocates the objective to the behavior the assistant is meant to generate. Including prompt loss also trains the input distribution, which changes the allocation of learning signal.


Worked example

Say we have one example: a hypothetical tokenized record with 47 tokens total; these counts are not the tokenizer output of the photosynthesis sentence. The first 38 are instruction + markers. The last 9 are response targets.

We build a batch of size 1. We need two tensors:

  • input_ids: shape (1, 47) — the full wrapped token sequence.
  • labels: shape (1, 47) — usually a copy of input_ids, but with the instruction positions set to -100 so PyTorch's cross-entropy function ignores them.

We push input_ids through the model. We get logits of shape (1, 47, vocab_size). The usual shift-by-one trick applies (position ii predicts token i+1i+1), so we compare logits at positions 0–45 against the labels at positions 1–46.

The nine response targets contribute local cross-entropy terms. Ignored targets contribute no direct term, but the response loss can backpropagate through prompt representations used by attention. The mask selects prediction targets, not a subset of input activations that are allowed to learn.

One loss.backward() and optimizer.step() update the trainable parameters. The resulting probability changes depend on the gradient, optimizer, learning rate, and interactions with other examples.


The LIMA experiment

LIMA fine-tuned a 65B LLaMA model on 1,000 curated prompt–response examples and reported strong human evaluations within its test setting. It showed that a small, carefully selected dataset can produce useful instruction following from a capable base.

What LIMA demonstrated

Model and data
A 65B LLaMA model fine-tuned on 1,000 curated prompt–response examples.
Evaluation
The paper evaluated its particular model and prompt distribution against several assistants.
What to test next
Whether a small set covers your tasks, languages, response formats, and safety behavior.

Where the data comes from

Three broad sources, and most modern recipes mix all three:

Human-written. A trained annotator reads an instruction and writes a response from scratch. Quality depends on annotator expertise and review. The original InstructGPT pipeline used this. Databricks' Dolly-15k is a famous open example.

Distilled from a stronger model. Generate candidate responses with a teacher, validate them, and use accepted examples to train the student. This can expand coverage, but generation and review cost money, and unchecked teacher errors can become training targets.

Self-instruct, where the model generates its own data. The Self-Instruct recipe starts from seed tasks, generates instructions and responses, filters them, and adds accepted rows to the training set. Evaluate the resulting task coverage and quality separately.

Recipes can combine sources. Tulu 3 documents an SFT mixture assembled for different capabilities; its data composition and ablations provide a concrete example.


Training settings

Check these settings against held-out examples:

Learning rate. Treat full fine-tuning and adapter tuning as different regimes. Start from a relevant recipe, then compare held-out task performance and retained capabilities; no single learning-rate range fits all model sizes and methods.

Epochs. Save checkpoints and use held-out evaluations to select the number of passes. Small datasets can overfit, and the useful stopping point varies by task and model.

Batch composition. Padding targets must be ignored. If packed examples should remain independent, use the corresponding attention boundaries and position handling. Length grouping can improve efficiency; check that it does not create an unintended curriculum. See Packing and Masking.

Prompt formatting consistency. Keep the training, evaluation, and inference templates compatible. Inspect role boundaries, whitespace, end-of-turn markers, and the resulting token IDs. A rendered string alone can hide a target-mask mismatch.


Implementation sketch

This training step applies a response-only loss mask to a causal language model. Preprocessing must identify the response boundaries in labels.

import torch
import torch.nn.functional as F
 
def sft_step(model, batch, optimizer):
    """
    batch["input_ids"]:    (B, T) — full wrapped sequence
    batch["labels"]:       (B, T) — same as input_ids, but
                                    prompt and padding positions set to -100
    batch["attention_mask"]: (B, T)
    """
    input_ids      = batch["input_ids"]
    labels         = batch["labels"]
    attention_mask = batch["attention_mask"]
 
    # Full forward pass over the whole sequence.
    # Response tokens need to attend to the prompt, so we can't
    # just drop the prompt tokens.
    logits = model(input_ids, attention_mask=attention_mask).logits
    # logits: (B, T, vocab)
 
    # Standard next-token shift.
    # Position i predicts token i+1.
    shift_logits = logits[..., :-1, :].contiguous()
    shift_labels = labels[..., 1:].contiguous()
 
    if not (shift_labels != -100).any():
        raise ValueError("batch has no scored next-token targets")
 
    # Cross-entropy ignores positions where label == -100.
    # That's how the prompt gets masked out of the loss.
    loss = F.cross_entropy(
        shift_logits.view(-1, shift_logits.size(-1)),
        shift_labels.view(-1),
        ignore_index=-100,
    )
 
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return loss.item()
 
def mask_prompt_labels(input_ids, prompt_lengths, attention_mask):
    # prompt_lengths counts non-padding prompt tokens, for left or right padding.
    positions = attention_mask.bool().long().cumsum(-1)
    prompt_lengths = torch.as_tensor(prompt_lengths, device=input_ids.device)
    labels = input_ids.clone()
    labels[positions <= prompt_lengths[:, None]] = -100
    labels[attention_mask == 0] = -100
    return labels
 
 

ignore_index=-100 implements the selected target mask.

Create masks during preprocessing and inspect decoded examples with their scored spans. In TRL, conversational assistant-only training uses SFTConfig(assistant_only_loss=True) and a compatible generation-aware chat template. Merely supplying a formatted conversation does not turn that setting on. Prompt-completion datasets have separate completion-loss defaults.


Checking for forgetting

Fine-tuning can reduce performance on tasks absent from the new data, but regression is not inevitable on every benchmark. The only way to know is to compare held-out evaluations before and after training.

Evaluate both the new task and retained capabilities

Target behavior
Held-out instruction following, formats, tool calls, or domain tasks.
Retained behavior
General knowledge, coding, languages, and other tasks relevant to the base model.
Checkpoint selection
Compare both sets before and after training. A lower training loss alone cannot show whether a regression occurred.
Evaluate the intended behavior and capabilities you want to retain. Neither improvement nor regression can be inferred from training loss alone.

Possible responses to a measured regression include changing the mixture, reducing update size, stopping at an earlier checkpoint, or using an adapter. A frozen base makes an adapter easy to remove; it does not bound how much the active adapter can change behavior. Replay data is another option when the original distribution and data are available.

What SFT can and cannot establish

SFT can teach facts and procedures present in its demonstrations, as well as style, safety behavior, and response structure. Learning those examples does not guarantee reliable factual recall or generalization. Retrieval can be a better fit when facts change frequently or answers need traceable evidence.

A small dataset can work when the base already has relevant capabilities and the examples cover the intended task. Broader languages, tools, and domains may require more coverage.

What's next

Demonstrations can express preferences through the answers selected for training. Pairwise comparisons provide a more explicit signal about which of two responses is preferred. The next post follows that signal through the RLHF pipeline: fitting a reward model and using it to guide policy updates.


Additional reading (and watching)