- DPO reparameterizes the reward function through policy ratios, eliminating the need for a separate reward model and PPO training.
- The method achieves comparable or better results than RLHF with roughly 3x less compute and only 1-2 hyperparameters to tune.
- Key limitation: DPO uses offline preference data only, so it can't correct distribution shift from novel model outputs during training.
Why DPO Changes Everything About LLM Alignment
RLHF works. We know this because GPT-4, Claude, and pretty much every useful LLM today uses it. But here’s the dirty secret: the RL part of RLHF is a nightmare to implement correctly. You need to train a reward model, then run PPO with careful clipping, KL penalties, advantage estimation, and a dozen hyperparameters that interact in ways nobody fully understands.
DPO throws all of that out. No reward model. No PPO. No actor-critic architecture. Just a single supervised learning objective that achieves the same result. You can read the full paper here.
The paper by Rafailov et al. (NeurIPS 2023) makes a mathematical observation that seems obvious in hindsight: if we know what the optimal policy looks like under a given reward function, we can invert that relationship and express the reward directly in terms of the policy. Then we never need to learn the reward model separately — we optimize the policy directly on preference data.

The Bradley-Terry Model and the DPO Trick
Standard RLHF uses the Bradley-Terry model to learn preferences. Given two responses (preferred) and (rejected) to a prompt , the probability that is preferred follows:
where is the sigmoid function and is the reward model. You train this reward model on human preference data, then use PPO to maximize while staying close to a reference policy (usually the SFT model).
The constrained optimization objective for RLHF is:
Here’s where DPO gets clever. The authors show that the optimal policy for this objective has a closed-form solution:
where is a partition function. The key insight: you can rearrange this to express the reward in terms of the policy:
Plug this back into the Bradley-Terry preference model, and the terms cancel out (since both and share the same prompt). You get the DPO objective:
This is just cross-entropy loss on preference pairs. No reward model, no value function, no GAE, no PPO clipping. Standard supervised learning infrastructure handles it.
What This Actually Looks Like in Code
The implementation is surprisingly simple. Here’s the core loss computation (tested with transformers 4.36+):
import torch
import torch.nn.functional as F
def compute_dpo_loss(
policy_chosen_logps: torch.Tensor,
policy_rejected_logps: torch.Tensor,
reference_chosen_logps: torch.Tensor,
reference_rejected_logps: torch.Tensor,
beta: float = 0.1,
) -> torch.Tensor:
"""Compute DPO loss for a batch of preference pairs.
All inputs are log probabilities of the full sequence,
computed by summing token-level log probs.
"""
# Log ratios: how much more likely is each response under policy vs reference?
chosen_log_ratios = policy_chosen_logps - reference_chosen_logps
rejected_log_ratios = policy_rejected_logps - reference_rejected_logps
# The margin we want to maximize
logits = beta * (chosen_log_ratios - rejected_log_ratios)
# Binary cross-entropy: push logits positive (chosen > rejected)
losses = -F.logsigmoid(logits)
# Useful for debugging: what fraction of pairs does the policy get "right"?
chosen_rewards = beta * chosen_log_ratios.detach()
rejected_rewards = beta * rejected_log_ratios.detach()
accuracy = (chosen_rewards > rejected_rewards).float().mean()
return losses.mean(), accuracy
The tricky part isn’t the loss — it’s computing the log probabilities correctly. You need to sum the per-token log probs for the response portion only, masking out the prompt:
def get_sequence_logps(model, input_ids, attention_mask, labels):
"""Get log probability of generating the response tokens.
labels should have -100 for prompt tokens (ignored in loss).
"""
with torch.no_grad() if not model.training else torch.enable_grad():
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
logits = outputs.logits
# Shift for autoregressive: predict token t from position t-1
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
shift_mask = (shift_labels != -100)
# Per-token log probs
log_probs = F.log_softmax(shift_logits, dim=-1)
token_log_probs = torch.gather(
log_probs,
dim=-1,
index=shift_labels.clamp(min=0).unsqueeze(-1)
).squeeze(-1)
# Mask and sum over response tokens
token_log_probs = token_log_probs * shift_mask
sequence_log_probs = token_log_probs.sum(dim=-1)
return sequence_log_probs
One gotcha I noticed: you need to keep the reference model frozen and in eval mode. I’ve seen implementations where both models share weights initially and diverge during training — that’s wrong. The reference log probs should be computed once at the start or with a completely separate model copy.
DPO vs PPO: The Numbers That Matter
The paper compares DPO against PPO-based RLHF across multiple tasks. On the TL;DR summarization task (fine-tuning GPT-J 6B), DPO achieves a win rate of 61% against human reference summaries, compared to 57% for PPO. But the really compelling numbers are about efficiency:
| Method | GPU Hours (estimated) | Hyperparameters to Tune | Memory Overhead |
|---|---|---|---|
| PPO (RLHF) | ~3x baseline | 8-12 (clip ratio, GAE λ, etc.) | 2x (actor + critic) |
| DPO | 1x baseline | 1-2 (just β, maybe lr) | 1.5x (policy + frozen ref) |
I covered PPO’s sensitivity to hyperparameters in PPO Training Diverges After 1M Steps: Clipping & LR Fixes, and honestly, DPO sidesteps almost all of those issues.
On the Anthropic-HH dialogue dataset, DPO reaches higher rewards with fewer compute steps. The paper shows DPO converging in about 1 epoch while PPO takes 3-4 epochs to reach similar performance. But here’s what surprised me most from the ablations: DPO is remarkably robust to values between 0.1 and 0.5. PPO’s clipping range and learning rate interactions are far more brittle.

The Parameter: More Subtle Than It Looks
The parameter controls how much the policy can deviate from the reference. High means “stay close to SFT” (conservative). Low means “aggressively optimize preferences” (potentially degenerate).
This term acts as an implicit KL penalty. When is large, even small deviations from the reference get amplified, making the loss steep for drifting policies. When is small, the model can move further from the reference to maximize preference margins.
The authors use for most experiments. I’m not entirely sure why this specific value works so well across different model sizes and datasets — the paper doesn’t provide much intuition beyond “we tuned it.” My best guess is that it balances responsiveness to preferences against maintaining coherent generation from the SFT foundation.
What the Paper Gets Right (and What It Doesn’t Say)
The theoretical contribution is elegant. Recognizing that you can reparameterize the reward through the policy ratio is the kind of insight that seems obvious after someone else discovers it. The proof that DPO optimizes the same objective as RLHF (under the Bradley-Terry assumption) is clean.
But there are limitations the authors acknowledge:
Single-turn only: DPO operates on static preference pairs. For multi-turn conversations where quality depends on the full trajectory, you can’t just sum independent pairwise preferences. The paper punts on this.
Assumes Bradley-Terry: Real human preferences might not follow logistic difference models. What if preferences are intransitive? What if the margin matters, not just the binary choice?
No online data collection: Unlike PPO, DPO can’t generate new responses during training and get feedback on them. You’re limited to your offline preference dataset. If the SFT policy produces out-of-distribution responses, DPO has no mechanism to correct them.
One thing the paper doesn’t emphasize enough: DPO assumes your preference data is correctly labeled. In practice, labeler disagreement rates of 20-30% are common. With PPO, the reward model can learn to smooth over noisy labels. With DPO, you’re fitting noise directly.
When Would I Actually Use DPO?
If I’m fine-tuning a 7B model with a few thousand preference pairs on a single GPU, DPO is the obvious choice. The implementation fits in a few hundred lines, hyperparameter tuning is minimal, and you don’t need to debug PPO’s credit assignment issues.
For production systems at scale — like training a flagship model with millions of preference comparisons — I’d be more cautious. The lack of online learning means you can’t iterate on model outputs. Anthropic and OpenAI still use variants of RLHF with online sampling, presumably because it handles distribution shift better.
That said, DPO has spawned useful variants. IPO (Identity Preference Optimization) removes the Bradley-Terry assumption. KTO (Kahneman-Tversky Optimization) works with binary good/bad labels instead of paired comparisons. If you’re debugging why your model prefers certain responses, the DPO loss landscape is far easier to interpret than PPO’s actor-critic dynamics.
Grabbing Designing Machine Learning Systems helped me reason about where preference learning fits in the broader ML ops pipeline — something the academic papers rarely discuss.
The Ablation That Surprised Me
Table 2 in the paper compares DPO against “reward + best-of-n” sampling. You train a reward model, generate n responses, pick the highest-scoring one. This baseline is surprisingly competitive — often within a few points of DPO.
What surprised me: best-of-128 sampling sometimes beats DPO on specific metrics. The implication is that a well-trained reward model contains most of the signal, and how you use that signal (RL vs. reranking) might matter less than we thought.
Of course, best-of-n is 128x more expensive at inference time. DPO gives you the preference-aligned policy with single-pass generation. But for offline evaluation or low-volume applications, reranking remains a strong baseline that’s often overlooked.
FAQ
Q: Does DPO work with models smaller than 7B parameters?
Yes, DPO works down to 1B parameter models, though the gains over SFT alone become marginal below ~3B. The key requirement is that your base model can already generate coherent responses — DPO steers preferences, it doesn’t teach fluency.
Q: How much preference data do I need for effective DPO training?
The paper uses 90K-170K preference pairs. In practice, you can see improvements with as few as 5K high-quality pairs. Quality matters more than quantity — 5K carefully curated pairs often outperform 50K noisy crowdsourced ones.
Q: Can DPO and RLHF be combined?
Yes, some practitioners use DPO for initial alignment then PPO for fine-grained control with online feedback. The DPO-tuned model serves as a better starting point for PPO than raw SFT. This hybrid approach is underexplored in the literature.
References
- Rafailov et al., “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” NeurIPS 2023. arXiv:2305.18290
- Ouyang et al., “Training language models to follow instructions with human feedback,” NeurIPS 2022. arXiv:2203.02155
- Schulman et al., “Proximal Policy Optimization Algorithms,” 2017. arXiv:1707.06347
- Stiennon et al., “Learning to summarize with human feedback,” NeurIPS 2020. arXiv:2009.01325
For aligning small-to-medium models with limited preference data, DPO is the clear winner over PPO-based RLHF. The math is cleaner, the implementation is simpler, and the results are comparable or better. If you need online learning or have concerns about preference distribution coverage, keep PPO in your toolkit — but start with DPO.
I’m curious whether the DPO variants (IPO, KTO, ORPO) will eventually subsume the original formulation. The field is moving fast, and I suspect we’ll see preference optimization become a standard post-training step with about as much complexity as adding dropout. We’re not there yet, but DPO made that future feel plausible.
Did you find this helpful?
Your support keeps this blog running and ad-free content coming.
☕ Buy me a coffeeMost Popular Posts
- Custom Metaclass in Python: 43% Faster Validation (12,793 views)
- Python match-case: 7 Patterns That Beat if-elif Chains (947 views)
- YOLOv8 INT8 Quantization: 4x Faster on Jetson Orin (759 views)
- yfinance Alternatives 2026: 7 Free APIs Compared (652 views)
- PaddleOCR vs EasyOCR vs Tesseract: Why PaddleOCR Is Slower (549 views)