Skip to content

DPO and Its Variants: Skipping the Reward Model

Scroll horizontally to read the full diagram.

Reward-model-based RLHF requires preference-model training, policy rollouts, and policy optimization. PPO compares the updated policy with the rollout policy; a separate KL penalty can compare it with a frozen reference. A critic may add another trainable model. Can we use preference pairs without that online optimization loop?

Rafailov et al.'s Direct Preference Optimization (2023) shows that the optimal RLHF policy, the one that maximizes expected reward while staying close to the reference in KL, can be written as a closed-form function of the reference policy and the reward. That relationship is invertible: you can express the reward as a function of the policy. Plug that into the preference likelihood and the reward model disappears. What's left is a supervised loss over log-probabilities. One trainable policy and a frozen reference (or cached reference scores), without PPO.

Prerequisites

This post assumes you've read:


From a regularized reward objective to preference loss

RLHF → DPO in five lines
1. RLHF objective
max  E[ r(x, y) ]  −  β · KL(π_θ || π_ref)

Maximize expected reward minus a KL penalty for departing from the reference policy.

2. Closed-form optimal policy
π*(y | x)  =  (1 / Z(x)) · π_ref(y | x) · exp( r(x, y) / β )

Solve for the policy that maximizes the objective. It's the reference distribution reweighted by exp(reward / β), normalized by a partition function Z(x).

3. Invert to express reward
r(x, y)  =  β · log ( π*(y | x) / π_ref(y | x) )  +  β · log Z(x)

Solve the previous line for r(x, y). The reward is just a log-ratio plus a log-partition term that only depends on x.

4. Plug into Bradley–Terry
P(y_w ≻ y_l | x)  =  σ( r(x, y_w) − r(x, y_l) )

Bradley–Terry says the probability of preferring y_w over y_l is a sigmoid of the reward difference. The log Z(x) terms cancel because they're identical for both responses.

5. The DPO loss
L_DPO  =  − log σ(  β · log π_θ(y_w|x)/π_ref(y_w|x)  −  β · log π_θ(y_l|x)/π_ref(y_l|x)  )

The loss uses the policy and reference log-probabilities of both responses, through the sigmoid of their scaled log-ratio difference.

Five lines from the RLHF objective to the DPO loss. In line 4, the partition function log Z(x) cancels because it's the same for both responses in a preference pair.

RLHF's optimal policy is the reference distribution tilted by exp(reward/β). That tilt can be inverted: the reward is β times the log-ratio between policy and reference, plus a term that only depends on the prompt. Bradley–Terry turns a reward difference into a preference probability; the prompt-only term cancels in the subtraction; and you're left with a sigmoid of a difference of log-ratios. Training that sigmoid as a binary classifier with cross-entropy is the DPO loss.

Concretely, for a single preference pair (x,yw,yl)(x, y_w, y_l) where ywy_w is the chosen response and yly_l is the rejected one:

LDPO(x,yw,yl)=−log⁡σ(βlog⁡πθ(yw∣x)πref(yw∣x)−βlog⁡πθ(yl∣x)πref(yl∣x))\mathcal{L}_{\text{DPO}}(x, y_w, y_l) = -\log \sigma\left(\beta \log \frac{\pi_\theta(y_w | x)}{\pi_{\text{ref}}(y_w | x)} - \beta \log \frac{\pi_\theta(y_l | x)}{\pi_{\text{ref}}(y_l | x)}\right)

The loss uses four log-probabilities: policy and reference scores for each response. Subtract the two log-ratios, scale by β, then apply the negative log-sigmoid.

DPO replaces separate reward-model training and PPO with a loss that uses the log-ratio βlog⁡πθ(y∣x)πref(y∣x)\beta \log \frac{\pi_\theta(y | x)}{\pi_{\text{ref}}(y | x)}, which is the implicit reward DPO is optimizing. If you want to know what reward your DPO-trained model has learned, you compute exactly that quantity and treat it like a scalar reward on any (x, y) pair.

The prompt-only normalization cancels because all compared responses share the same prompt. This also works under the Plackett–Luce ranking model: the original DPO paper derives that extension in Appendix A.3. Pairwise Bradley–Terry is the simpler case, not the only one where cancellation is possible.


What a DPO training step actually does

You have a batch of preference pairs. For each one, you run four response scores, often computed in two batched forward passes: the policy on ywy_w, the policy on yly_l, the reference on ywy_w, and the reference on yly_l. The reference passes are with no_grad and often cached. The reference doesn't change during training, so once you've computed those log-probs for a given dataset, you can store them and skip those forward passes entirely in subsequent epochs.

You get four log-probabilities per pair. You compute two ratios, subtract, scale by β, pass through log-sigmoid, take the negative mean over the batch, and backprop. Exactly the computational shape of a supervised fine-tuning step, just with a different loss function.

Constructed response log-ratios; β = 0.1

Chosen log(π / π_ref)
0.0
Rejected log(π / π_ref)
0.0
Scaled margin
0.000
DPO loss
0.693

DPO rewards a larger difference. It can improve this objective even when both response probabilities decrease.

Constructed examples show what the loss rewards: an increase in the chosen-minus-rejected log-ratio margin. Both response probabilities can fall while this margin improves.

In the regularized reward objective used to derive DPO, larger β\beta means a stronger KL penalty and an optimum closer to the reference for fixed rewards. In the implemented loss, β\beta also scales the margin and its gradients, so changing it is not simply changing the learning rate.

For a separable pair, logistic loss approaches zero as the margin grows, and its gradient approaches zero too. There is no finite preferred margin, though finite training, conflicting pairs, and parameter sharing affect the result. A larger training margin does not by itself establish better held-out behavior.

The reference stays fixed, but DPO does not explicitly measure a global KL on fresh policy samples at each update. Its observed training signal comes from dataset pairs; behavior outside their coverage depends on generalization and parameter sharing.


Worked example

Say the prompt is "Explain transformers in one sentence." The chosen response ywy_w is a reasonable one-sentence explanation. The rejected response yly_l is a joke about robots from Michael Bay movies. We pass each through both the current policy and the frozen reference, and we get four log-probabilities.

constructed preference pair → computed DPO loss
prompt x
“Explain transformers in one sentence.”
chosen y_w
“Neural nets that use attention to weight which parts of the input matter for each output.”
rejected y_l
“Transformers are a type of robot.”
chosen
log π_θ(y_w | x)-12.4
log π_ref(y_w | x)-13.8
rejected
log π_θ(y_l | x)-18.2
log π_ref(y_l | x)-16.9
log π_θ(y_w)/π_ref(y_w)
-12.4 − (-13.8) = +1.4
log π_θ(y_l)/π_ref(y_l)
-18.2 − (-16.9) = -1.3
margin (implicit reward gap)
m = β · (1.4 − (-1.3)) = 0.1 · 2.7 = 0.270
DPO loss for this pair
L = −log σ(0.270) = 0.567
P(y_w ≻ y_l | x) = σ(0.270) = 56.7%

A preference pair: the human preferred y_w over y_l, given prompt x.

Toy log probabilities give a scaled margin of 0.27, loss about 0.567, and an implicit preference probability about 56.7%. This is not the probability of generating the chosen answer.

The four log probabilities are assumed values. Only the two policy scores have gradients; the reference scores are fixed. For this pair, lowering the loss increases the relative margin, though the realized changes in either response probability depend on all shared model parameters and other examples.


The minimal implementation

The code below computes response scores and the pairwise loss, then updates the policy while keeping the reference fixed.

import torch
import torch.nn.functional as F
 
def dpo_loss(
    policy_chosen_logps: torch.Tensor,   # log π_θ(y_w | x)   shape: (batch,)
    policy_rejected_logps: torch.Tensor, # log π_θ(y_l | x)   shape: (batch,)
    ref_chosen_logps: torch.Tensor,      # log π_ref(y_w | x) shape: (batch,)
    ref_rejected_logps: torch.Tensor,    # log π_ref(y_l | x) shape: (batch,)
    beta: float = 0.1,
) -> tuple[torch.Tensor, torch.Tensor]:
    """
    DPO loss for a batch of preference pairs.
 
    Each input is a 1-D tensor of per-sequence log-probabilities. You get those
    by summing per-token log-probs over the response tokens (not the prompt).
    """
    # Log-ratio of policy vs reference on the chosen response
    chosen_logratio = policy_chosen_logps - ref_chosen_logps.detach()
    # Log-ratio of policy vs reference on the rejected response
    rejected_logratio = policy_rejected_logps - ref_rejected_logps.detach()
 
    # The implicit-reward margin
    margin = beta * (chosen_logratio - rejected_logratio)
 
    # DPO loss: -log σ(margin) = softplus(-margin)
    loss = -F.logsigmoid(margin).mean()
 
    # Fraction of pairs with positive implicit-reward margin, relative to reference
    with torch.no_grad():
        accuracy = (margin > 0).float().mean()
 
    return loss, accuracy
 
def sequence_logp(model, ids, response_mask, attention_mask):
    logits = model(ids, attention_mask=attention_mask).logits[:, :-1].float()
    token_logps = logits.log_softmax(-1).gather(-1, ids[:, 1:, None]).squeeze(-1)
    mask = response_mask[:, 1:].bool() & attention_mask[:, 1:].bool()
    if not mask.any(-1).all():
        raise ValueError("every response needs a scored next-token target")
    return (token_logps * mask).sum(-1)
 
# Each chosen/rejected IDs tensor contains prompt + response.
# The corresponding response masks select targets; attention masks exclude padding.
def dpo_step(policy, ref, batch, optimizer, beta=0.1):
    policy.eval()  # disables dropout; eval mode still allows gradients
    ref.eval()
    def score(model, side):
        return sequence_logp(model, batch[f"{side}_ids"],
                             batch[f"{side}_mask"], batch[f"{side}_attention_mask"])
    p_chosen, p_rejected = score(policy, "chosen"), score(policy, "rejected")
    with torch.no_grad():
        r_chosen, r_rejected = score(ref, "chosen"), score(ref, "rejected")
    loss, acc = dpo_loss(p_chosen, p_rejected, r_chosen, r_rejected, beta)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return loss.item(), acc.item()
 
 

The helper sums response-token log probabilities and disables dropout while retaining policy gradients.


Where DPO falls short, and the variants that patch it

Variants of DPO change the margin objective or the feedback format.

The first is overfitting to preference margins. Logistic loss has no finite minimizer on a perfectly separable pair, even though its gradient tends to zero for a large positive margin. IPO uses a squared objective with a finite target. In the unscaled log-ratio convention below, that target is τ=1/(2β)\tau=1/(2\beta); practical implementations can also change sequence-length normalization:

LIPO=(log⁡πθ(yw∣x)πref(yw∣x)−log⁡πθ(yl∣x)πref(yl∣x)−τ)2\mathcal{L}_{\text{IPO}} = \left(\log \frac{\pi_\theta(y_w | x)}{\pi_{\text{ref}}(y_w | x)} - \log \frac{\pi_\theta(y_l | x)}{\pi_{\text{ref}}(y_l | x)} - \tau\right)^2

When the margin reaches τ, the loss bottoms out and stops pushing. The policy is allowed to learn "prefer y_w over y_l by some amount," not "push the margin to infinity."

The second is data format. KTO supports unpaired desirable/undesirable feedback. It uses reference-relative rewards, a KL-based reference point, and weights for the two label types. Those weights are configuration choices, not a requirement that every rejected example always count more than every chosen one.

ORPO combines chosen-response likelihood with an odds-ratio preference term, avoiding a separate reference policy.

Preference objectives use different inputs

DPO
Paired responses and a reference policy; logistic loss on a difference of log probability ratios.
IPO
Paired responses and a reference; a squared objective with a finite target log-ratio margin, using its own normalization.
KTO
Desirable/undesirable labels need not be paired; reference-relative rewards and a KL-based reference point.
ORPO
Chosen-response likelihood plus an odds-ratio preference penalty; no separate reference policy.

These are not four functions of the same scalar margin.

Compare data requirements and objectives. KTO and ORPO use objectives that cannot be represented solely by DPO’s pairwise margin.

SimPO uses length-normalized response log probabilities and a target margin without a separate reference policy.

Compare these methods’ memory use and held-out performance with model, data, and training budget controlled.


When DPO matches RLHF, and when it doesn't

Offline DPO repeatedly trains on a fixed set of labeled pairs. If current outputs differ substantially from those pairs, evaluation on the training set may say little about current behavior. Online sampling can expose those outputs for new labels or reward estimates, but the feedback may still be wrong outside the judge’s training distribution.

constructed response-space sketch (not measured embeddings)
labeled preference pairs(near π_ref)π_θ samplesassumed drift (%)0toy samples inside the marked data region98%

Scroll horizontally to read the full diagram.

The illustrated policy overlaps the offline data region.

A constructed coverage sketch. The moving cloud is an assumed shift, not a measured relationship between response length and preference coverage.

The distinction is not permanently attached to the optimizer: DPO can be run iteratively on fresh pairs, and PPO can optimize an unreliable reward model. Published comparisons need to separate algorithm, sampling distribution, data quality, and reward-model quality. Tulu 3 is a concrete example combining SFT, DPO, and reinforcement learning with verifiable rewards.


Misconceptions

"DPO has no reward interpretation." It does not train a separate reward network. The derivation gives an implicit reward, βlog⁡[πθ(y∣x)/πref(y∣x)]\beta \log[\pi_\theta(y|x)/\pi_{\text{ref}}(y|x)], up to a prompt-dependent constant. That score can rank responses, but the quality of those rankings outside the training pairs still needs evaluation.

"DPO is just SFT on chosen answers." SFT maximizes target log probability. DPO fits a preference margin involving chosen and rejected responses relative to a reference. Neither objective guarantees that all reasonable completions increase in probability; normalized distributions and shared parameters couple them.

"DPO requires a separate SFT stage mathematically." It needs a policy and reference with suitable support, not a theorem-mandated SFT stage. Starting from an instruction-tuned model is common and can help. Whether DPO from a different starting point works is an empirical question.

"DPO's β is the same as PPO's KL penalty coefficient." They play analogous roles, in that both control how far the policy can drift from the reference. But they do different things. PPO's KL term penalizes divergence from the reference on the current policy's samples at each step. DPO's β scales the implicit reward margin and only regularizes on the preference pairs in the dataset. A DPO policy is free to be wildly different from π_ref on prompts no one labeled. This is closely related to why DPO struggles with distribution shift on long generations.

What's next

The next post covers Constitutional AI and RLAIF, where written principles guide model critiques and some preference labels, alongside human feedback.


Additional reading (and watching)