SFT / DPO / RLHF
Modern LLMs are trained through a multi-stage pipeline that typically begins with pre-training on massive web corpora, followed by supervised fine-tuning (SFT), and then alignment through preference optimization (DPO, RLHF) or reinforcement learning. Each stage serves a distinct purpose in shaping model behavior.
SFT teaches the model to follow instructions and produce coherent completions. DPO directly optimizes for human preferences without a separate reward model. RLHF introduces a learned reward model to guide the policy through reinforcement learning (PPO). Understanding when and how to use each method is critical for building aligned, capable models.
This guide provides a detailed comparison of these training paradigms, with practical code examples for implementing each approach using established libraries and frameworks.
SFT is the first post-training stage for most LLMs. The pre-trained base model is fine-tuned on high-quality instruction-response pairs to learn the format and style of helpful conversation. SFT uses standard autoregressive language modeling with cross-entropy loss computed only on the response tokens.
Key characteristics of SFT include: teacher forcing (the model sees the correct response during training), maximum likelihood estimation (MLE) objective, and label masking to exclude prompt tokens from loss computation. SFT is simple, stable, and computationally efficient compared to RL-based methods.
| 1 | import torch |
| 2 | from datasets import load_dataset |
| 3 | from trl import SFTTrainer |
| 4 | from transformers import TrainingArguments, AutoModelForCausalLM, AutoTokenizer |
| 5 | |
| 6 | model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-3B") |
| 7 | tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B") |
| 8 | tokenizer.pad_token = tokenizer.eos_token |
| 9 | |
| 10 | dataset = load_dataset("json", data_files="training_data.jsonl") |
| 11 | |
| 12 | training_args = TrainingArguments( |
| 13 | output_dir="./sft-model", |
| 14 | per_device_train_batch_size=4, |
| 15 | gradient_accumulation_steps=4, |
| 16 | learning_rate=2e-4, |
| 17 | warmup_steps=100, |
| 18 | num_train_epochs=3, |
| 19 | logging_steps=25, |
| 20 | save_strategy="epoch", |
| 21 | bf16=True, |
| 22 | report_to="wandb" |
| 23 | ) |
| 24 | |
| 25 | trainer = SFTTrainer( |
| 26 | model=model, |
| 27 | args=training_args, |
| 28 | train_dataset=dataset["train"], |
| 29 | tokenizer=tokenizer, |
| 30 | max_seq_length=2048, |
| 31 | dataset_text_field="text", |
| 32 | ) |
| 33 | |
| 34 | trainer.train() |
best practice
DPO is a preference optimization method that directly optimizes the policy from pairwise preference data without training a separate reward model. It reformulates the RLHF objective into a simple binary classification loss that compares the likelihood of chosen vs rejected responses under the trained policy, relative to a reference model.
The DPO loss function takes the form: -log(sigmoid(beta * (log(pi(y_w|x)/pi_ref(y_w|x)) - log(pi(y_l|x)/pi_ref(y_l|x))))) where y_w is the preferred response, y_l is the dispreferred response, pi is the trained policy, and pi_ref is the frozen reference model. The beta parameter controls how far the policy can deviate from the reference.
| Property | DPO | RLHF (PPO) |
|---|---|---|
| Reward Model | Not needed | Required (separate model) |
| Training Stability | High | Moderate โ sensitive to KL penalty |
| Computational Cost | Low (2 forward passes) | High (RL loop + reward model) |
| Data Efficiency | High | Moderate |
| Online Sampling | No (offline) | Yes (on-policy) |
DPO Training Implementation
| 1 | from trl import DPOTrainer |
| 2 | from datasets import Dataset |
| 3 | |
| 4 | # Preference dataset format |
| 5 | preference_data = [ |
| 6 | { |
| 7 | "prompt": "Explain quantum computing", |
| 8 | "chosen": "Quantum computing uses qubits that can exist in superposition...", |
| 9 | "rejected": "Quantum computing is when computers are really fast..." |
| 10 | }, |
| 11 | ] |
| 12 | |
| 13 | dataset = Dataset.from_list(preference_data) |
| 14 | |
| 15 | dpo_trainer = DPOTrainer( |
| 16 | model=model, |
| 17 | ref_model=None, # Auto-initialized from model |
| 18 | args=training_args, |
| 19 | train_dataset=dataset, |
| 20 | tokenizer=tokenizer, |
| 21 | beta=0.1, # KL divergence coefficient |
| 22 | max_length=2048, |
| 23 | max_prompt_length=512, |
| 24 | ) |
| 25 | |
| 26 | dpo_trainer.train() |
pro tip
RLHF is the three-stage pipeline pioneered by InstructGPT that consists of: (1) SFT on human demonstrations, (2) training a reward model on human preferences, and (3) optimizing the policy against the reward model using PPO. This approach allows the model to generate novel responses that are optimized for human preferences, not just imitate the training data.
Stage 1: Reward Model Training
The reward model is trained on comparison data โ pairs of responses where one is preferred over the other. It learns to assign higher scores to preferred responses. The reward model is typically initialized from the same base model as the policy but with a scalar output head replacing the language modeling head.
| 1 | import torch.nn as nn |
| 2 | from transformers import AutoModelForSequenceClassification |
| 3 | |
| 4 | reward_model = AutoModelForSequenceClassification.from_pretrained( |
| 5 | "meta-llama/Llama-3.2-3B", |
| 6 | num_labels=1, # Single scalar reward |
| 7 | torch_dtype=torch.bfloat16 |
| 8 | ) |
| 9 | |
| 10 | # Reward model training loop |
| 11 | optimizer = torch.optim.AdamW(reward_model.parameters(), lr=1e-5) |
| 12 | |
| 13 | for batch in dataloader: |
| 14 | chosen_rewards = reward_model(batch["chosen_ids"]).logits |
| 15 | rejected_rewards = reward_model(batch["rejected_ids"]).logits |
| 16 | |
| 17 | # Bradley-Terry preference loss |
| 18 | loss = -nn.functional.logsigmoid( |
| 19 | chosen_rewards - rejected_rewards |
| 20 | ).mean() |
| 21 | |
| 22 | loss.backward() |
| 23 | optimizer.step() |
Stage 2: PPO Training
PPO (Proximal Policy Optimization) optimizes the policy model to maximize the reward signal from the reward model while staying close to the reference model via a KL divergence penalty. This prevents the policy from exploiting the reward model by generating nonsensical but high-scoring text.
| 1 | from trl import PPOConfig, PPOTrainer |
| 2 | from trl.core import LengthSampler |
| 3 | |
| 4 | ppo_config = PPOConfig( |
| 5 | model_name="meta-llama/Llama-3.2-3B", |
| 6 | learning_rate=1.41e-5, |
| 7 | batch_size=16, |
| 8 | mini_batch_size=4, |
| 9 | gradient_accumulation_steps=1, |
| 10 | ppo_epochs=4, |
| 11 | kl_penalty=0.05, |
| 12 | clip_range=0.2, |
| 13 | init_kl_coef=0.05, |
| 14 | target_kl=0.1, |
| 15 | ) |
| 16 | |
| 17 | # Setup PPO trainer |
| 18 | ppo_trainer = PPOTrainer( |
| 19 | config=ppo_config, |
| 20 | model=model, |
| 21 | ref_model=ref_model, |
| 22 | tokenizer=tokenizer, |
| 23 | dataset=dataset, |
| 24 | ) |
| 25 | |
| 26 | # PPO training step |
| 27 | for query_tensors, response_tensors in ppo_trainer.dataloader: |
| 28 | rewards = reward_model( |
| 29 | torch.cat([query_tensors, response_tensors], dim=-1) |
| 30 | ).logits |
| 31 | |
| 32 | stats = ppo_trainer.step(query_tensors, response_tensors, rewards) |
| 33 | ppo_trainer.log_stats(stats) |
warning
Choosing between SFT, DPO, and RLHF depends on your data, compute budget, and quality requirements. SFT is the minimum viable alignment method. DPO offers a practical middle ground. RLHF provides the highest potential quality but at significantly greater complexity and cost.
Preference datasets are the fuel for both DPO and RLHF. Each example consists of a prompt, a chosen (preferred) response, and a rejected (dispreferred) response. High-quality preference data is difficult and expensive to collect, often requiring human annotators with domain expertise.
Common sources of preference data include: human annotation (labelers compare model outputs), AI feedback (another LLM judges outputs), implicit feedback (user clicks, dwell time), and synthetic data (using a reward model to rank candidates).
| Dataset | Size | Source | Use Case |
|---|---|---|---|
| Anthropic HH-RLHF | 170K | Human feedback | Harmlessness, helpfulness |
| OpenAI Summarize | 93K | Human comparisons | Summarization alignment |
| UltraFeedback | 64K | GPT-4 feedback | General preference |
| Nectar | 183K | GPT-4 ranked | Multi-task alignment |
info