Em 2023, Rafailov et al. provaram um resultado surpreendente: o reward model do RLHF é desnecessário. A política ótima tem forma fechada que permite expressar reward em termos da própria política — colapsando três modelos (policy, reference, RM) e um algoritmo RL (PPO) numa única loss de classificação. Esse paper (DPO) reabriu o pipeline e em dois anos IPO, KTO, SLiC, SPPO, ORPO e outros nasceram. Este módulo é o mapa do novo terreno.
A derivação que tornou o RM dispensável
O RLHF maximiza um objective com KL penalty: max_π E_x,y[r(x,y)] - β·KL(π || π_ref). Esse problema tem solução fechada (lagrangiano + KKT):
Invertendo para isolar o reward: r(x,y) = β·log(π*(y|x)/π_ref(y|x)) + β·log Z(x). Substituindo na loss Bradley-Terry (que treina o RM), log Z(x) cancela entre y_w e y_l (mesmo x). Resultado: loss puramente em probabilidades da política:
Geometricamente: DPO aumenta a probabilidade de y_w e diminui a de y_l, mas normalizado pela referência (não pode fugir muito de π_ref). É treinamento de classificação binária — sem rollouts, sem RM, sem KL penalty explícita (a KL está implícita na razão log(π/π_ref)).
DPO em código (TRL)
from trl import DPOTrainer, DPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
# Dataset com colunas: prompt, chosen, rejected
dataset = load_dataset("HuggingFaceH4/ultrafeedback_binarized", split="train_prefs")
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
ref_model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
config = DPOConfig(
output_dir="./dpo-llama3",
beta=0.1, # β da equação — controla a "rigidez" do KL implícito
loss_type="sigmoid", # DPO clássico. Alternativas: ipo, hinge, kto_pair, robust
learning_rate=5e-7, # MUITO menor que SFT — DPO é sensível
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
num_train_epochs=1, # >1 epoch frequentemente piora
max_length=2048,
max_prompt_length=1024,
)
trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=config,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()β baixo (0.01) → DPO se afasta muito do SFT, pode degradar capacidades. β alto (0.5+) → mal sai do SFT, ganho marginal. β = 0.1–0.3 é típico.
IPO: regularizando preferências ruidosas
Azar et al. 2023 (DeepMind, arxiv.org/abs/2310.12036) observou: DPO via Bradley-Terry assume preferências determinísticas no limite (P(y_w > y_l) → 1). Quando dataset é ruidoso (preferências contraditórias), DPO empurra modelo a colocar probabilidade quase nula em y_l — overfit. IPO substitui a loss:
A MSE com margem fixa τ regulariza: o modelo busca diferença τ entre y_w e y_l, não diferença infinita. Resultado: menos overfit em datasets ruidosos. Empíricamente IPO é mais robusto, DPO converge mais rápido em datasets limpos.
KTO: aversão à perda na loss
KTO (Ethayarajh et al., Stanford/ContextualAI 2024 — arxiv.org/abs/2402.01306) parte de duas premissas: (1) datasets reais são point-wise (👍/👎), não pareados; (2) Prospect Theory (Kahneman & Tversky 1979 — Nobel 2002) diz que humanos pesam perdas mais que ganhos. Loss assimétrica:
# KTO loss (simplificada — Ethayarajh et al. 2024)
import torch
import torch.nn.functional as F
def kto_loss(policy_logps, ref_logps, is_desirable,
beta=0.1, lambda_D=1.0, lambda_U=1.0):
"""
policy_logps: log-probs do modelo treinável para o exemplo (não pareado)
ref_logps: log-probs do modelo de referência
is_desirable: True se o exemplo é "good" (👍), False se é "bad" (👎)
"""
# Reward implícito: log-ratio com referência
rewards = beta * (policy_logps - ref_logps)
# KL baseline (estimado em batch — não é par a par)
kl = (policy_logps - ref_logps).mean().detach().clamp(min=0)
if is_desirable:
# Valor positivo se reward > kl
value = lambda_D * (1 - torch.sigmoid(rewards - kl))
else:
# Aversão à perda: penaliza fortemente se reward > kl
value = lambda_U * (1 - torch.sigmoid(kl - rewards))
return valueλ_D e λ_U controlam o tradeoff. Defaults λ_D = λ_U = 1 são neutros. Em datasets desbalanceados (90% positivos), usar λ_U > λ_D para evitar que o modelo ignore os negativos minoritários.
Comparação direta dos três
| Dimensão | DPO | IPO | KTO |
|---|---|---|---|
| Formato dataset | Pareado (chosen/rejected) | Pareado | Point-wise (good/bad) |
| Loss base | log σ (BT) | MSE com margem | Asymmetric Prospect Theory |
| Robustez a ruído | Baixa | Alta (regularizado) | Média (depende de λ) |
| Velocidade convergência | Rápida | Média | Média |
| Hiperparâmetros | β | β, τ | β, λ_D, λ_U |
| Quando vence | Pareado limpo | Pareado ruidoso | Point-wise / desbalanceado |
| Paper | Rafailov NeurIPS 2023 | Azar 2023 | Ethayarajh 2024 |
📋 Você tem logs de produção com 👍/👎 dos usuários (não pareado), 85% positivos.
Dataset point-wise + desbalanceado é o cenário canônico do KTO. λ_U > λ_D faz o modelo prestar atenção aos 15% de negativos. DPO/IPO exigiriam pareamento sintético (caro e introduz vieses).
Alt: Pareamento sintético + DPO —
Alt: BCO ou SLiC-HF —
Família estendida em 2026
Fluxo de decisão prático
Perguntas frequentes
❓ Posso pular o SFT e ir direto pro DPO no modelo base?
❓ DPO 'duas vezes' funciona? (DPO → DPO com novo dataset)
❓ DPO funciona com LoRA?
❓ Existe DPO sem ref_model?