This chapter covers the family of methods that extend or replace DPO with different objectives, data assumptions, or architectural trade-offs. Each addresses a specific limitation of standard offline DPO: distribution shift (Online DPO), the need for paired data (KTO), overfitting to noisy labels (IPO), reference model memory cost (ORPO), or training complexity (Best-of-N).

Online DPO

Motivation

Standard DPO’s primary limitation: the preference data was generated by a different model (often an older checkpoint or even a different model family). As training progresses, the policy generates text that looks nothing like the training pairs → the loss is optimizing on an irrelevant distribution.

Online DPO solution[127]: Generate fresh preference pairs from the current policy at every step, judge them with a reward model, then apply the DPO loss.

Algorithm

  1. Generate K responses per prompt from current πθ
  2. Score all responses with reward model rϕ
  3. Create pairs: highest-scoring = chosen, lowest-scoring = rejected
  4. Apply DPO loss on these fresh pairs
  5. Repeat (new generation every step)

TRL Implementation

The following shows a minimal working example using HuggingFace TRL.

from trl import OnlineDPOConfig, OnlineDPOTrainer
from transformers import AutoModelForCausalLM, AutoModelForSequenceClassification
 
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
    torch_dtype=torch.bfloat16)
reward_model = AutoModelForSequenceClassification.from_pretrained(
    "RLHFlow/ArmoRM-Llama3-8B-v0.1", torch_dtype=torch.bfloat16)
 
online_dpo_config = OnlineDPOConfig(
    output_dir="./online_dpo_output",
    learning_rate=5e-7,
    beta=0.1,                    # DPO beta (same meaning as standard DPO)
    num_generations=4,           # K responses per prompt
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    max_new_tokens=512,
    temperature=0.7,
    bf16=True,
    num_train_epochs=1,
    logging_steps=10,
)
 
trainer = OnlineDPOTrainer(
    model=model,
    reward_model=reward_model,
    args=online_dpo_config,
    train_dataset=prompt_dataset,
    tokenizer=tokenizer,
)
trainer.train()

Online DPO vs Offline DPO vs PPO

DataModelsLossBest ForOffline DPOStatic pairs2 (policy + reference)DPOQuick alignment, limited computeOnline DPOFresh from πθ3 (policy + reference + reward model)DPOWhen DPO plateaus, need explorationPPOFresh from πθ4 (policy + reference + reward model + value head)PPO clipMax quality, complex reasoning

KTO — Kahneman-Tversky Optimization

Motivation

DPO requires paired preferences: for the same prompt, you need both a good and bad response. In practice, most feedback is unpaired: users give thumbs up/down on individual responses, with no matched pair.

KTO’s insight[86]: Use prospect theory (from behavioral economics). Humans feel losses more strongly than gains. A “thumbs down” should produce a stronger gradient than a “thumbs up.”

Loss Function

ℒKTO=𝔼yw​[λw​(1−v⁡(x,yw))]+𝔼yl​[λl⋅v⁡(x,yl)](8.1)

where v⁡(x,y)=σ⁡(β​log⁡πθ​(y|x)πref​(y|x)−zref), and zref is the expected KL divergence (a running baseline).

TRL Implementation

The following shows a minimal working example using HuggingFace TRL.

from trl import KTOConfig, KTOTrainer
 
# Dataset format: {"prompt": str, "completion": str, "label": bool}
# label=True for desirable, label=False for undesirable
kto_dataset = [
    {"prompt": "What's 2+2?", "completion": "The answer is 4.", "label": True},
    {"prompt": "What's 2+2?", "completion": "It might be 5.", "label": False},
]
 
kto_config = KTOConfig(
    output_dir="./kto_output",
    beta=0.1,
    desirable_weight=1.0,        # Weight for good examples
    undesirable_weight=1.0,      # Weight for bad examples (increase for loss aversion)
    learning_rate=5e-7,
    max_length=2048,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    num_train_epochs=1,
    bf16=True,
)
 
trainer = KTOTrainer(
    model=model,
    ref_model=ref_model,  # Or None with LoRA
    args=kto_config,
    train_dataset=kto_dataset,
    tokenizer=tokenizer,
)
trainer.train()

When to Choose KTO

  • You have binary feedback but not matched pairs
  • Production thumbs-up/down data at scale
  • One class dominates (e.g., 90% good, 10% bad) — KTO handles imbalance better
  • Rapid iteration with noisy labels (more robust than DPO to noise)

IPO — Identity Preference Optimization

Motivation

DPO has a degenerate solution: it can achieve zero loss by making the margin between chosen and rejected infinitely large. In practice, this means DPO overfits — pushing chosen probability to 1 and rejected to 0, memorizing training data.

IPO’s fix[14]: Instead of log-sigmoid (which saturates), use a squared loss that targets a specific margin. The loss is minimized at a finite gap, not at infinity.

Loss Function

ℒIPO=𝔼⁡[(log⁡πθ​(yw|x)πref​(yw|x)−log⁡πθ​(yl|x)πref​(yl|x)−12​β)2](8.2)

TRL Implementation

The following shows a minimal working example using HuggingFace TRL.

from trl import DPOConfig, DPOTrainer
 
# IPO is implemented as a DPO loss_type variant in TRL
ipo_config = DPOConfig(
    output_dir="./ipo_output",
    beta=0.1,
    loss_type="ipo",             # The key difference!
    learning_rate=5e-7,
    max_length=2048,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    bf16=True,
    num_train_epochs=1,
)
 
trainer = DPOTrainer(
    model=model, ref_model=None, args=ipo_config,
    train_dataset=pref_dataset, tokenizer=tokenizer, peft_config=lora_config,
)
trainer.train()

When to Choose IPO over DPO

  • Noisy preference data (crowdsourced, AI-judged with errors)
  • Observing DPO overfitting (train loss → 0 but eval degrades)
  • Want more conservative, robust alignment
  • Multiple epochs needed (DPO degrades after epoch 1; IPO is more stable)

ORPO — Odds Ratio Preference Optimization

Motivation

All methods so far need a reference model — either as a separate copy (doubles memory) or implicitly via LoRA. ORPO [141] eliminates the reference entirely by combining SFT and preference alignment in a single loss.

Key insight: Use the odds ratio of generating chosen vs rejected as the preference signal. The SFT component naturally prevents collapse (no need for KL regularization).

Loss Function

ℒORPO=ℒSFT​(yw)⏟standard NLL on chosen−λ⋅log⁡σ⁡(log⁡oddsθ​(yw|x)oddsθ​(yl|x))⏟preference alignment via odds ratio(8.3)

where oddsθ​(y|x)=Pθ​(y|x)1−Pθ​(y|x).

TRL Implementation

The following shows a minimal working example using HuggingFace TRL.

from trl import ORPOConfig, ORPOTrainer
 
orpo_config = ORPOConfig(
    output_dir="./orpo_output",
    beta=0.1,                    # Odds ratio weight (lambda)
    learning_rate=5e-7,
    max_length=2048,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    bf16=True,
    num_train_epochs=1,
    gradient_checkpointing=True,
)
 
trainer = ORPOTrainer(
    model=model,                 # No ref_model needed!
    args=orpo_config,
    train_dataset=pref_dataset,  # Same format as DPO: prompt/chosen/rejected
    tokenizer=tokenizer,
    peft_config=lora_config,
)
trainer.train()

When to Choose ORPO

  • Memory-constrained: can’t afford reference model copy (saves 70–140GB for 70B)
  • Starting from base model (not SFT’d yet) — ORPO does SFT simultaneously
  • Want simplest possible pipeline: one model, one loss, one training run
  • Good preference data available from the start

Best-of-N Sampling (Rejection Sampling)

Motivation

Sometimes the simplest approach wins. Best-of-N [262] requires no training at all during the RL phase — just generate multiple candidates and pick the best one.

Algorithm

  1. For each prompt, generate N responses from the policy (typically N=4–64)
  2. Score all responses with a reward model
  3. Select the highest-scoring response
  4. (Optional) Use selected responses as SFT data for the next iteration
Best-of-N response:y∗=argmaxyi∼πθ(⋅|x)rϕ(x,yi)(8.4)

TRL Implementation

The following shows a minimal working example using HuggingFace TRL.

from transformers import pipeline
import numpy as np
 
# Inference-time Best-of-N (manual implementation)
gen_pipeline = pipeline("text-generation", model=model, tokenizer=tokenizer)
 
def best_of_n(prompt, n=16, temperature=0.8):
    """Generate N candidates and return the highest-reward one."""
    candidates = gen_pipeline(
        prompt, num_return_sequences=n,
        temperature=temperature, do_sample=True, max_new_tokens=512,
    )
    scores = [reward_model.score(prompt, c["generated_text"]) for c in candidates]
    return candidates[np.argmax(scores)]["generated_text"]
 
best_response = best_of_n(prompt, n=16)
 
# Training: Rejection Sampling Fine-Tuning (RFT)
from trl import SFTConfig, SFTTrainer
 
# Step 1: Generate and filter
all_responses = []
for prompt in prompts:
    candidates = [generate(prompt, temp=0.9) for _ in range(16)]
    scores = [reward_model.score(prompt, c) for c in candidates]
    best_idx = np.argmax(scores)
    if scores[best_idx] > threshold:  # Quality gate
        all_responses.append({"prompt": prompt, "completion": candidates[best_idx]})
 
# Step 2: SFT on best responses
sft_config = SFTConfig(output_dir="./rft_output", learning_rate=2e-5, num_train_epochs=2, max_seq_length=2048)
trainer = SFTTrainer(model=model, args=sft_config, train_dataset=all_responses, tokenizer=tokenizer)
trainer.train()
# Step 3: Repeat from Step 1 with updated model (iterative RFT)

Scaling Laws for Best-of-N

NQuality GainCostNotes1Baseline1×Standard sampling4+5–8% win-rate4×Minimum useful. Good cost/quality ratio16+10–15% win-rate16×Strong. Often matches PPO quality64+15–20% win-rate64×Diminishing returns start256+18–22% win-rate256×Only for critical applications

Summary: Choosing an Alignment Method

We have now surveyed the full landscape of preference optimization and RL-based alignment methods. This section consolidates the key trade-offs into a single reference to help practitioners select the right approach for their constraints.

MethodModelsDataComputeStabilityBest For
PPO4Online (gen)Very highLowMax quality, complex reasoning
GRPO2 (no critic)Online (gen)HighMediumMath/code (verifiable rewards)
DPO2Offline pairsLowHighStyle/safety, limited compute
Online DPO3Online (gen)MediumMedium-HighDPO without distribution shift
KTO2Unpaired binaryLowHighProduction feedback, thumbs up/down
IPO2Offline pairsLowVery highNoisy labels, anti-overfitting
ORPO1Offline pairsVery lowHighMemory-limited, SFT+align combined
Best-of-N1+RMOnline (gen)MediumPerfectStrong baseline, data generation

Table 8.1:Cross-method comparison of alignment approaches.

Figure 8.1: Approximate quality vs. compute frontier. Methods above the SFT ceiling line improve beyond what supervised
Figure 8.1:Approximate quality vs. compute frontier. Methods above the SFT ceiling line improve beyond what supervised fine-tuning alone achieves. Position is illustrative and model-dependent.