LLM Alignment and Fine-Tuning¶
Alignment is the process of taking a capable base model and making it behave the way you actually want. The gap between “predicts next tokens” and “follows instructions helpfully and safely” is non-trivial, and the methods used to close that gap have evolved rapidly from 2022 to 2026. This document covers the four main alignment paradigms in depth — SFT, RLHF/PPO, DPO, and GRPO — with honest cost/benefit analysis, the mathematics where they matter, and runnable code.
1. Supervised Fine-Tuning (SFT)¶
SFT is where every alignment pipeline starts. You take a pretrained base model and train it on a dataset of (instruction, response) pairs using standard cross-entropy loss. This teaches the model the format of instruction-following before any preference optimization.
Instruction Dataset Format¶
The de facto standard is the chat template format, pioneered by ShareGPT and adopted by every major model family:
{
"conversations": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain gradient descent in one paragraph."},
{"role": "assistant", "content": "Gradient descent is an iterative optimization..."}
]
}
What makes a good SFT dataset:
Quality over quantity: 10K high-quality examples outperform 1M noisy ones (verified by Alpaca → Alpaca-cleaned ablations)
Diversity: covers the distribution of tasks you care about
Format consistency: mixed templates cause training instability
No data contamination: eval set leakage inflates benchmark scores by 3-8% (documented in multiple papers)
Key public SFT datasets (2025):
Dataset |
Size |
Quality |
Notes |
|---|---|---|---|
OpenHermes-2.5 |
1M |
High |
GPT-4 generated, widely used |
Dolphin |
3.5M |
Medium-High |
Uncensored, diverse |
WizardLM-Evol |
70K |
High |
Evolved instruction complexity |
Alpaca-cleaned |
52K |
Medium |
Original Alpaca + deduplication |
FLAN collection |
1.8K tasks |
High |
Multi-task, Google Research |
SFT Training Loss¶
Standard cross-entropy on assistant tokens only (masking user/system tokens):
L_SFT = -Σ log P(y_t | y_{<t}, x)
where x is the instruction and y is the response. Masking user tokens is critical — training on those tokens causes the model to complete prompts rather than respond to them.
2. RLHF: Reinforcement Learning from Human Feedback¶
RLHF (Ouyang et al., 2022 — arXiv 2203.02155) is what turned GPT-3 into InstructGPT and established the modern alignment paradigm. It operates in two stages: reward model training and PPO fine-tuning.
Stage 1: Reward Model Training¶
Collect human preference data: show annotators pairs of model outputs (y_w, y_l) for the same prompt x, and have them choose the preferred response. Train a reward model R_φ using the Bradley-Terry model:
P(y_w ≻ y_l | x) = σ(R_φ(x, y_w) - R_φ(x, y_l))
The loss function:
L_RM = -E[(x, y_w, y_l) ~ D] [log σ(R_φ(x, y_w) - R_φ(x, y_l))]
The reward model is typically the SFT model with the final layer replaced by a scalar head. This is important: the reward model needs to understand language at the level of the policy model.
Stage 2: PPO Fine-Tuning¶
Optimize the policy π_θ to maximize expected reward while staying close to the SFT reference policy π_ref (via KL penalty):
max_{π_θ} E_{x~D, y~π_θ(·|x)} [R_φ(x, y) - β · KL(π_θ(·|x) || π_ref(·|x))]
The KL term is critical. Without it, the policy “reward hacks” — finds degenerate outputs that score high on R_φ but are nonsensical to humans (e.g., repetitive token sequences that exploit reward model weaknesses).
PPO memory requirement: 4 models simultaneously:
Policy model (being trained)
Reference model (frozen SFT model for KL computation)
Value model (PPO critic, typically same size as policy)
Reward model
For a 7B model at BF16: ~4 × 14GB = ~56GB minimum, before optimizer states. In practice, 80GB+ for stable training.
3. DPO: Direct Preference Optimization¶
DPO (Rafailov et al., NeurIPS 2023 — arXiv 2305.18290) is the mathematical insight that changes everything. Instead of training a separate reward model and running PPO, DPO directly optimizes the policy on preference data by exploiting a closed-form relationship between the optimal policy and the reward function.
The Reparameterization Trick¶
Start from the RLHF objective. The optimal policy under the KL-constrained reward maximization has a closed form:
π*(y|x) = (1/Z(x)) · π_ref(y|x) · exp(r*(x,y)/β)
where Z(x) is the partition function. Rearranging to solve for r*:
r*(x,y) = β · log(π*(y|x) / π_ref(y|x)) + β · log Z(x)
The key insight: the log Z(x) term cancels when you substitute this into the Bradley-Terry preference probability:
P(y_w ≻ y_l | x) = σ(β · log(π*(y_w|x)/π_ref(y_w|x)) - β · log(π*(y_l|x)/π_ref(y_l|x)))
This gives you the DPO loss — directly parameterized by the policy you’re training, no reward model needed:
L_DPO = -E[(x, y_w, y_l)] [log σ(β(log π_θ(y_w|x)/π_ref(y_w|x) - log π_θ(y_l|x)/π_ref(y_l|x)))]
Memory requirement: 2 models (policy + frozen reference). For 7B at BF16: ~28GB + optimizer states. Runs on 2×A100 40GB.
Known Limitations of DPO¶
Length bias: DPO-trained models systematically produce longer responses (documented in multiple ablations). The model learns that length correlates with higher human preference scores. Mitigation: length-normalize the reward signal, or use SimPO (arXiv 2405.14734) which adds an explicit length penalty.
Bradley-Terry assumption: DPO inherits the BT model’s assumption that preferences are transitive and can be represented by a scalar reward. IPO (Identity Preference Optimization, arXiv 2310.12036) relaxes this.
Distribution sensitivity: DPO is sensitive to the quality of rejected responses. If your rejected samples are too easy to distinguish, the model learns a superficial pattern.
4. Constitutional AI / RLAIF¶
Constitutional AI (Bai et al., Anthropic 2022 — arXiv 2212.08073) replaces human preference labelers with an AI critic operating according to a written “constitution” — a list of principles. The model critiques its own outputs, revises them, and generates synthetic preference pairs for DPO/RLHF.
Why it matters: Scales alignment without proportional human annotation cost. Anthropic uses this at production scale. The “RLAIF” generalization (replacing human raters with AI raters) is now standard at labs that can’t afford human annotation at scale.
Practical implication for you: When building domain-specific aligned models, you can use Claude/GPT-4 as the constitutional critic to generate preference pairs cheaply. This is a legitimate production pattern, not a hack.
5. GRPO: Group Relative Policy Optimization¶
GRPO (DeepSeekMath — arXiv 2402.03300; extended in DeepSeek-R1 — arXiv 2501.12948) is the most significant alignment development of 2024-2025. It proved that pure RL with verifiable rewards can elicit sophisticated reasoning capabilities without any human preference data.
How GRPO Works¶
For each prompt x, sample G completions {y_1, …, y_G} from the current policy. Compute a group-relative advantage for each:
A_i = (r_i - mean(r_1..r_G)) / std(r_1..r_G)
The GRPO objective:
L_GRPO = -E[Σ_i min(r_t(θ) · A_i, clip(r_t(θ), 1-ε, 1+ε) · A_i)] + β · KL(π_θ || π_ref)
where r_t(θ) = π_θ(y_i|x) / π_old(y_i|x) is the probability ratio (same as PPO clipping).
The key difference from PPO: No value network (critic). The advantage is computed from the group statistics, not from a learned value function. This removes the need for a separate critic model and simplifies training significantly.
When GRPO works: Only when you have a verifiable reward signal — math problems with ground-truth answers, code that either passes tests or doesn’t, logic puzzles with binary correct/incorrect. This is why DeepSeek applied it to math and code. It doesn’t work for open-ended generation where correctness is ambiguous.
Honest Cost/Benefit Table¶
Method |
GPU Memory (7B) |
Training Complexity |
Quality Ceiling |
Best For |
|---|---|---|---|---|
SFT only |
~16GB (BF16) |
Low |
Medium |
Format/style adaptation, task-specific fine-tune |
DPO |
~28GB + optimizer |
Medium |
High |
General alignment, chat models, 2024-2026 default |
PPO/RLHF |
~56GB+ |
Very High |
Highest* |
Production alignment at scale, OpenAI-style |
GRPO |
~28GB + optimizer |
Medium |
High (reasoning) |
Math, code, verifiable reasoning tasks |
Constitutional AI |
Same as DPO |
Medium |
High |
Scalable alignment, reduces human annotation |
*PPO edges DPO by ~2.5% on math and ~1.2% general benchmarks when data quality is held fixed (ICML 2024). The gap is real but often not worth the operational complexity.
Code: SFT + DPO with TRL¶
The TRL (Transformer Reinforcement Learning) library from HuggingFace is the practical standard.
# requirements: pip install trl transformers datasets peft accelerate bitsandbytes
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from trl import SFTTrainer, SFTConfig, DPOTrainer, DPOConfig
from peft import LoraConfig, get_peft_model
import torch
# ────────────────────────────────────────────────────────
# STAGE 1: Supervised Fine-Tuning
# ────────────────────────────────────────────────────────
MODEL_ID = "meta-llama/Llama-3.2-1B" # Use 1B for testing, 7B for real work
# 4-bit quantization config (QLoRA setup)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
quantization_config=bnb_config,
device_map="auto",
attn_implementation="flash_attention_2", # remove if not available
)
# LoRA config - target attention projection matrices
lora_config = LoraConfig(
r=16, # rank: 8-64, higher = more capacity
lora_alpha=32, # scaling factor, usually 2x rank
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
# Load instruction dataset in ChatML format
# Dataset should have a "messages" column with list of {"role": ..., "content": ...}
dataset = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft[:5000]")
sft_config = SFTConfig(
output_dir="./sft_output",
max_seq_length=2048,
num_train_epochs=1,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # effective batch = 16
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.03,
logging_steps=10,
save_steps=100,
bf16=True,
gradient_checkpointing=True,
dataset_text_field="prompt", # or use dataset_kwargs
packing=False, # set True to pack short sequences for efficiency
)
sft_trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=lora_config,
processing_class=tokenizer,
args=sft_config,
)
sft_trainer.train()
sft_trainer.save_model("./sft_output/final")
# ────────────────────────────────────────────────────────
# STAGE 2: DPO Fine-Tuning
# ────────────────────────────────────────────────────────
# DPO dataset requires: {"prompt": str, "chosen": str, "rejected": str}
dpo_dataset = load_dataset("HuggingFaceH4/ultrafeedback_binarized", split="train_prefs[:2000]")
# Use SFT model as both policy (trainable) and reference (frozen)
dpo_config = DPOConfig(
output_dir="./dpo_output",
num_train_epochs=1,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=5e-5,
beta=0.1, # KL penalty coefficient; 0.1-0.5 typical range
loss_type="sigmoid", # standard DPO; alternatives: "ipo", "kto_pair"
max_length=1024,
max_prompt_length=512,
bf16=True,
logging_steps=10,
)
# Load SFT-tuned model for DPO
from peft import PeftModel
from transformers import AutoModelForCausalLM
base_model = AutoModelForCausalLM.from_pretrained(MODEL_ID, quantization_config=bnb_config, device_map="auto")
policy_model = PeftModel.from_pretrained(base_model, "./sft_output/final")
dpo_trainer = DPOTrainer(
model=policy_model,
ref_model=None, # None = use model with disabled adapters as reference
train_dataset=dpo_dataset,
processing_class=tokenizer,
args=dpo_config,
)
dpo_trainer.train()
dpo_trainer.save_model("./dpo_output/final")
print("DPO training complete. Merge LoRA weights before inference:")
print(" merged = policy_model.merge_and_unload()")
What to monitor during DPO training:
rewards/chosenshould increase,rewards/rejectedshould decreaserewards/margins(chosen - rejected) should be positive and growinglogps/chosenandlogps/rejecteddiverging = learning is happeningIf
kldivergence grows too fast, increasebeta
Papers Reference¶
Paper |
ArXiv ID |
Key Contribution |
|---|---|---|
InstructGPT / RLHF |
2203.02155 |
First production RLHF pipeline |
DPO |
2305.18290 |
Reward-free alignment, closed-form policy optimization |
Constitutional AI |
2212.08073 |
RLAIF, scalable alignment without human raters |
DeepSeekMath / GRPO |
2402.03300 |
Group relative policy optimization |
DeepSeek-R1 |
2501.12948 |
Pure RL elicits reasoning at o1-level without SFT data |
KTO |
2402.01306 |
Non-paired preference optimization |
SimPO |
2405.14734 |
DPO length bias fix |
IPO |
2310.12036 |
Beyond Bradley-Terry assumptions |
What Most People Get Wrong¶
Treating alignment methods as interchangeable plug-ins. DPO is not “easier RLHF” that you always use instead. GRPO is not “better DPO.” They solve different problems. DPO optimizes for human preferences on open-ended text. GRPO optimizes for verifiable correctness on tasks with ground truth. If you try GRPO on a customer support chatbot where “correct” is undefined, you’ll get garbage. Match the method to the reward signal you actually have.
Return to README.md · Next: 02_parameter_efficient_finetuning.md