Preference optimization: DPO, SimPO, KTO
entender por qué DPO puede alinear con preferencias sin entrenar un reward model ni hacer RL, derivar su "reward implícito", y elegir entre DPO/SimPO/KTO según los datos que tengas.
4.1El problema: imitar no basta
SFT (L3) enseña a imitar "la respuesta correcta". Pero a menudo no hay una única correcta: hay respuestas mejores y peores (más concisas, más seguras, mejor tono). Para eso necesitas comparaciones: "para este prompt, A es mejor que B".
El enfoque clásico (InstructGPT/RLHF) era: (1) entrenar un reward model sobre comparaciones, (2) hacer PPO usando ese reward. Caro y frágil. DPO (Rafailov et al. 2023) demostró que puedes saltarte ambos pasos.
4.2De Bradley-Terry al objetivo de DPO (la derivación que importa)
Modelo de preferencias de Bradley-Terry: la probabilidad de que la respuesta y_w (winner) se prefiera a y_l (loser) dado un prompt x depende de una función de recompensa latente r:
P(y_w ≻ y_l | x) = σ( r(x, y_w) − r(x, y_l) ) # σ = sigmoide
El RLHF clásico aprende r y luego optimiza la política π para maximizar r con una restricción KL hacia una referencia π_ref. La idea genial de DPO: la solución óptima de ese problema con restricción KL tiene forma cerrada, y permite expresar el reward en términos de la propia política:
r(x, y) = β · log( π(y|x) / π_ref(y|x) ) + constante
Es decir, el reward está implícito en cuánto la política se desvía de la referencia. Sustituyendo en Bradley-Terry, el reward model desaparece y queda una pérdida que se optimiza directamente sobre la política con datos de preferencia:
L_DPO = − E[ log σ( β·log(π(y_w|x)/π_ref(y_w|x)) − β·log(π(y_l|x)/π_ref(y_l|x)) ) ]
Lectura intuitiva: sube la probabilidad de la respuesta preferida y baja la de la rechazada, relativo a la referencia, con fuerza controlada por β. Sin reward model, sin RL, sin muestreo online — es aprendizaje supervisado sobre pares.
- β (beta): controla cuánto puede alejarse la política de
π_ref. β alto = conservador (cerca de la referencia); β bajo = agresivo (más cambio, más riesgo de degradar). Típico 0.1.
4.3Variantes que debes conocer
- SimPO: elimina el modelo de referencia (usa la longitud media como normalización). Más barato en memoria (no cargas
π_ref), a veces igual de bueno. Útil cuando la VRAM aprieta. - KTO (Kahneman-Tversky Optimization): no necesita pares; usa señales binarias sueltas (👍/👎 por respuesta). Es lo que quieres cuando tienes feedback de tipo "esto estuvo bien / mal" pero no comparaciones emparejadas — mucho más fácil de recopilar en producción.
- ORPO: combina SFT y preferencias en una sola fase (sin referencia, sin SFT previo). Eficiente.
Cuándo cada uno: pares disponibles → DPO; VRAM justa → SimPO; solo señales binarias → KTO; quieres una sola fase → ORPO.
4.4Laboratorio L4.1 — DPO sobre tu modelo SFT
Partimos del adaptador SFT (L3) y lo alineamos con preferencias. Para datos genéricos usamos un dataset de preferencias estándar; en tu caso real, las preferencias salen de comparar salidas de tu propio modelo.
1# lab_n2l4_dpo.py — DPO sobre un modelo ya SFT, con Unsloth + TRL
2from unsloth import FastLanguageModel
3from datasets import load_dataset
4from trl import DPOConfig, DPOTrainer
5
6model, tok = FastLanguageModel.from_pretrained(
7 "unsloth/Qwen3-4B-Instruct", max_seq_length=2048, load_in_4bit=True)
8model = FastLanguageModel.get_peft_model(model, r=16, lora_alpha=16,
9 target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
10 use_gradient_checkpointing="unsloth")
11
12# Dataset de preferencias: cada fila tiene prompt, chosen, rejected
13ds = load_dataset("trl-lib/ultrafeedback_binarized", split="train[:5000]")
14
15trainer = DPOTrainer(
16 model=model, ref_model=None, # con PEFT, la referencia = base sin adaptador (TRL lo gestiona)
17 args=DPOConfig(
18 beta=0.1, # fuerza de la preferencia (ver 4.2)
19 per_device_train_batch_size=2, gradient_accumulation_steps=4,
20 learning_rate=5e-6, # DPO usa LR MUCHO menor que SFT
21 num_train_epochs=1, logging_steps=20,
22 optim="paged_adamw_8bit", max_length=1024, max_prompt_length=512,
23 output_dir="./dpo_out",
24 ),
25 train_dataset=ds, processing_class=tok,
26)
27trainer.train()Líneas no triviales:
ref_model=Nonecon PEFT: TRL usa el base sin adaptadores comoπ_ref(desactiva el adaptador para calcular la referencia). No necesitas cargar un segundo modelo → ahorras VRAM. Esto es clave para que DPO quepa en la 5090.learning_rate=5e-6: DPO es mucho más sensible que SFT; LR alto colapsa la política. Típico 1e-6 a 5e-6.beta=0.1: empieza aquí; si el modelo se degrada (olvida capacidades), súbelo; si no cambia nada, bájalo.- Métricas a vigilar en wandb:
rewards/chosen,rewards/rejected,rewards/margins(debe crecer), yrewards/accuracies(fracción de pares donde chosen > rejected; debe subir hacia 1).
4.5Laboratorio L4.2 — Generar tus propios pares de preferencia (genérico)
En la práctica, los mejores pares salen de tu propio modelo: generas varias respuestas, las puntúas (con una métrica, un juez, o feedback humano) y formas pares (mejor=chosen, peor=rejected).
1# pseudocódigo del patrón "on-policy preference data"
2# 1) para cada prompt, genera N respuestas con el modelo SFT (temperature>0)
3# 2) puntúa cada una (verificador, métrica, o LLM-as-judge local — un Qwen3-14B en tu 5090)
4# 3) chosen = argmax score, rejected = argmin score
5# 4) DPO sobre esos pares
6# Ventaja: las preferencias son "on-policy" (sobre lo que tu modelo realmente genera),
7# lo que suele alinear mejor que datasets genéricos.4.6Ejercicios
E1. Entrena DPO con beta=0.1 y beta=0.5. Compara rewards/margins y la calidad final. ¿Qué hace β a la agresividad del cambio?
E2. Implementa el patrón de L4.2: genera 4 respuestas por prompt con tu SFT, púntalas con exact-match (en una tarea verificable) y forma pares. Entrena DPO con esos pares on-policy vs con el dataset genérico. ¿Cuál alinea mejor en tu tarea?
E3. Tienes feedback de producción del tipo 👍/👎 (sin pares). ¿DPO o KTO? Justifícalo y entrénalo con KTOTrainer.
4.7Trampas comunes
- LR de SFT (2e-4) en DPO → colapso. DPO quiere ~5e-6.
- Olvidar que con PEFT la referencia es gratis (
ref_model=None); cargar un segundo modelo y hacer OOM. - No vigilar
rewards/accuracies: si no sube, tus pares están mal etiquetados.
4.8Referencias
- DPO (Rafailov et al., NeurIPS 2023), SimPO (Meng et al. 2024), KTO (Ethayarajh et al. 2024), ORPO (Hong et al. 2024). Docs TRL (DPOTrainer, KTOTrainer).