volver al blog

DPO frente a RLHF: alinear un modelo sin aprendizaje por refuerzo

llmfine-tuningalineamiento

Alinear no es enseñar, es elegir

Un modelo recién preentrenado sabe muchísimo y no sabe lo que quieres. Ha visto foros, manuales y contratos, y todos pesan más o menos lo mismo. Cuando le pides que responda a un cliente enfadado, no tiene forma de intuir que prefieres la respuesta sobria a la respuesta graciosa.

Ese hueco no se cierra con más datos ni con más parámetros. Se cierra con preferencias: pares de respuestas donde alguien ha señalado cuál es mejor. La pregunta interesante es qué haces con esos pares. Durante años la respuesta canónica fue RLHF, con su modelo de recompensa y su PPO. Desde 2023 hay un camino considerablemente más corto que llega al mismo sitio.

El camino largo: RLHF en tres etapas

RLHF (reinforcement learning from human feedback) es una tubería de tres pasos, y cada uno arrastra su propia infraestructura.

Primero, un ajuste supervisado: coges las respuestas buenas y entrenas al modelo a imitarlas. Sencillo y estable.

Segundo, entrenas un modelo de recompensa. Es otro modelo, normalmente del mismo tamaño o algo menor, que aprende de los pares de preferencia a puntuar respuestas con un número. Su trabajo es aproximar el criterio humano.

Tercero, optimizas la política con PPO contra esa recompensa. El modelo genera respuestas, el modelo de recompensa las puntúa, el algoritmo empuja los pesos hacia lo que puntúa alto. Con una penalización KL contra el modelo original para que no se aleje demasiado y empiece a producir galimatías con recompensa muy alta.

El resultado funciona. El coste es que en ese tercer paso tienes cuatro modelos vivos a la vez: la política que entrenas, la referencia congelada, el modelo de recompensa y el crítico de PPO. Y estás muestreando texto en cada paso de entrenamiento, lo que hace el bucle lento y sensible a los hiperparámetros. Cualquiera que haya intentado reproducir una tirada de PPO sabe que “inestable” es un eufemismo amable.

La intuición de DPO: el modelo ya lleva dentro su recompensa

El artículo de Direct Preference Optimization parte de una observación elegante. En el planteamiento estándar de RLHF, la política óptima para una recompensa dada tiene una forma cerrada conocida. Si esa relación es invertible, puedes despejarla al revés: expresar la recompensa en función de la política.

Traducido, la recompensa implícita de una respuesta es cuánto más probable la hace tu modelo comparado con el modelo de referencia. No hace falta entrenar un modelo aparte para puntuar: la puntuación ya está codificada en la razón de probabilidades entre la política y su referencia congelada.

Con eso, el problema entero se derrumba en una pérdida de clasificación binaria. Para cada par (respuesta preferida, respuesta rechazada), empujas al modelo a que el margen entre ambas razones de probabilidad sea positivo:

pérdida = -log σ( β · [ (log π(y_bueno) - log π_ref(y_bueno))
                      - (log π(y_malo)  - log π_ref(y_malo)) ] )

Nada de muestrear. Nada de crítico. Nada de modelo de recompensa. Dos pasadas hacia delante, una por el modelo que entrenas y otra por la referencia congelada, y retropropagas. El parámetro β hace el papel de la penalización KL: cuanto más alto, más pegado se queda el modelo a su referencia.

En la práctica cabe en una pantalla

La librería TRL de Hugging Face implementa esto directamente. El grueso del trabajo es el dataset, no el entrenamiento.

from datasets import load_dataset
from trl import DPOConfig, DPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer

# Punto de partida: un modelo ya ajustado por SFT sobre respuestas buenas.
# DPO refina preferencias, no enseña el formato desde cero.
modelo = AutoModelForCausalLM.from_pretrained("./mi-modelo-sft")
tok = AutoTokenizer.from_pretrained("./mi-modelo-sft")

# El dataset necesita exactamente tres columnas:
#   prompt   -> la petición
#   chosen   -> la respuesta preferida
#   rejected -> la descartada
datos = load_dataset("json", data_files="preferencias.jsonl", split="train")

config = DPOConfig(
    output_dir="./modelo-dpo",
    beta=0.1,              # más alto = más conservador frente a la referencia
    learning_rate=5e-7,    # órdenes de magnitud menor que en SFT: DPO se pasa de frenada
    num_train_epochs=1,    # con dos épocas suele empezar a sobreajustar el margen
    per_device_train_batch_size=4,
)

# Sin ref_model explícito, TRL congela una copia del modelo inicial
# y la usa como referencia. Dos modelos en memoria, no cuatro.
DPOTrainer(model=modelo, args=config, train_dataset=datos, processing_class=tok).train()

Dos detalles que separan una tirada que funciona de una que degrada el modelo: la tasa de aprendizaje va uno o dos órdenes de magnitud por debajo de la del SFT, y una época suele bastar. DPO optimiza un margen, y un margen se puede inflar arruinando la calidad absoluta de ambas respuestas.

Qué eliges al elegir

RLHF con PPODPO
Modelos en memoriaCuatro: política, referencia, recompensa y críticoDos: política y referencia
Fase de muestreoSí, genera en cada pasoNo, el dataset es estático
Hiperparámetros críticosMuchos y acopladosBásicamente β y la tasa de aprendizaje
EstabilidadDelicada, difícil de reproducirAlta, se comporta como un ajuste normal
Datos nuevosPuede explorar más allá del datasetLimitado a lo que hay en los pares
Recompensa reutilizableSí, el modelo de recompensa vale para otras cosasNo, queda implícita en los pesos

Esa penúltima fila es la concesión de verdad. DPO es off-policy: aprende solo de las respuestas que ya están en tu dataset. Si tus pares vienen de un modelo peor que el que entrenas, le estás enseñando a preferir dentro de un rango de calidad que ya ha superado. PPO, al muestrear del modelo actual, puede descubrir comportamientos que nadie etiquetó.

Y la última fila importa si tienes ambiciones a medio plazo. Un modelo de recompensa entrenado se reutiliza: para filtrar generaciones, para hacer best-of-n en inferencia, para evaluar candidatos. DPO no te deja ese artefacto; la preferencia se disuelve en los pesos y no la puedes consultar.

Dónde DPO se queda corto

Conviene saber los bordes antes de chocar con ellos.

El más citado es el desplazamiento de verosimilitud: DPO puede reducir la probabilidad de la respuesta preferida y de la rechazada a la vez, con tal de que el margen crezca. El entrenamiento parece ir bien mientras el modelo empeora. Vigila la log-probabilidad absoluta de chosen, no solo el margen.

El segundo es la calidad de los pares. Si tus anotadores discrepan o etiquetaron por longitud en vez de por criterio, DPO aprende esa correlación con una obediencia incómoda. Es la vía más rápida a un modelo que contesta largo porque largo puntuaba mejor.

Han aparecido variantes que atacan estos puntos: IPO regulariza el sobreajuste al margen, KTO prescinde de pares y trabaja con etiquetas sueltas de bueno o malo, ORPO fusiona el SFT y la preferencia en una sola fase sin modelo de referencia. Ninguna ha desbancado a DPO como opción por defecto, pero merece la pena conocerlas cuando la tirada base no cuadra.

Por dónde empezar

Si tienes un modelo en producción y un montón de conversaciones con pulgar arriba y pulgar abajo, ya tienes un dataset de preferencias. No necesitas montar una tubería de aprendizaje por refuerzo para aprovecharlo.

Empieza por ahí: 1.000 pares limpios, un SFT decente como punto de partida, β en 0,1 y una época. Mide con un conjunto de evaluación que el entrenamiento no haya visto y compara contra el modelo sin alinear, no contra tus expectativas. La mitad de las veces descubrirás que el problema no era el alineamiento, sino el prompt, y te habrás ahorrado el entrenamiento entero.

La lección que deja el artículo de DPO va más allá de la técnica concreta. Un procedimiento complicado no es prueba de que el problema lo sea. A veces solo significa que aún nadie había mirado bien la ecuación.

Fuentes