# TRL (entrenamiento RLHF/DPO)

**TRL** (Transformer Reinforcement Learning) es la biblioteca oficial de HuggingFace para entrenar modelos de lenguaje con técnicas de aprendizaje por refuerzo. Con más de 10K estrellas en GitHub, ofrece implementaciones de vanguardia de RLHF, DPO, PPO, GRPO y otros algoritmos de alineación para LLMs.

{% hint style="success" %}
Todos los ejemplos se pueden ejecutar en servidores GPU alquilados a través de [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

***

## ¿Qué es TRL?

TRL es la biblioteca detrás de muchos de los modelos de lenguaje mejor alineados hoy en día. Proporciona:

* **SFT (Ajuste fino supervisado)** — afinamiento de instrucciones estándar con formato ChatML
* **RLHF/PPO** — Proximal Policy Optimization clásico con un modelo de recompensa
* **DPO** — Optimización Directa por Preferencias (¡no se necesita modelo de recompensa!)
* **GRPO** — Group Relative Policy Optimization (método de DeepSeek-R1)
* **KTO** — Kahneman-Tversky Optimization (funciona con preferencias no emparejadas)
* **Modelado de Recompensas** — entrenar un modelo de recompensa a partir de datos de preferencias humanas
* **IterativeSFT** — RL en línea con una configuración más simple
* **ORPO** — Odds Ratio Preference Optimization

TRL se integra de forma nativa con el ecosistema HuggingFace: `transformers`, `peft`, `datasets`, `accelerate`, y `bitsandbytes`.

{% hint style="info" %}
**¿Qué algoritmo deberías usar?**

* **DPO** — el más simple y estable. Úsalo cuando tengas datos de preferencias emparejadas (elegido/rechazado).
* **PPO** — el más poderoso pero complejo. Úsalo cuando tengas un modelo de recompensa o una función de puntuación.
* **GRPO** — excelente para tareas de razonamiento/matemáticas. Método de entrenamiento de DeepSeek-R1.
* **SFT** — comienza siempre aquí antes de aplicar cualquier método de RL.
  {% endhint %}

***

## Requisitos del servidor

| Componente     | Mínimo                    | Recomendado                     |
| -------------- | ------------------------- | ------------------------------- |
| GPU            | RTX 3090 (24 GB)          | A100 80 GB / H100               |
| VRAM           | 16 GB (SFT/DPO 7B + LoRA) | 80 GB (afinamiento completo 7B) |
| RAM            | 32 GB                     | 64 GB+                          |
| CPU            | 8 núcleos                 | 16+ núcleos                     |
| Almacenamiento | 100 GB                    | 300 GB+                         |
| SO             | Ubuntu 20.04+             | Ubuntu 22.04                    |
| Python         | 3.9+                      | 3.11                            |
| CUDA           | 11.8+                     | 12.1+                           |

### VRAM por tarea

| Tarea | Modelo      | Método      | VRAM             |
| ----- | ----------- | ----------- | ---------------- |
| SFT   | Llama 3 8B  | QLoRA 4-bit | \~8 GB           |
| DPO   | Llama 3 8B  | LoRA        | \~20 GB          |
| PPO   | Llama 3 8B  | Completo    | \~80 GB (2×A100) |
| GRPO  | Qwen 7B     | LoRA        | \~24 GB          |
| SFT   | Llama 3 70B | QLoRA 4-bit | \~48 GB          |
| DPO   | Llama 3 70B | LoRA        | \~80 GB          |

***

## Puertos

| Puerto | Servicio | Notas                                                         |
| ------ | -------- | ------------------------------------------------------------- |
| 22     | SSH      | Acceso al terminal, transferencia de archivos, monitorización |

TRL es una biblioteca de entrenamiento — se ejecuta como un script CLI/Python, no se requiere servidor web.

***

## Instalación en Clore.ai

### Paso 1 — Alquilar un servidor

1. Ir a [Clore.ai Marketplace](https://clore.ai/marketplace)
2. Filtrar por **VRAM ≥ 24 GB** (RTX 3090, A100 o H100)
3. Elige una **PyTorch** o **CUDA 12.1** imagen base
4. Seleccionar **Almacenamiento ≥ 200 GB** para modelos y conjuntos de datos
5. Abrir puerto **22** para acceso SSH

### Paso 2 — Conectar vía SSH

```bash
ssh root@<server-ip> -p <ssh-port>
```

### Paso 3 — Instalar TRL

```bash
# Crear entorno virtual de Python
python3 -m venv /opt/trl
source /opt/trl/bin/activate

# Instalar TRL con todas las dependencias
pip install trl

# Instalar dependencias adicionales para flujos de trabajo completos
pip install \
    transformers \
    datasets \
    peft \
    accelerate \
    bitsandbytes \
    wandb \
    scipy \
    sentencepiece \
    protobuf

# Verificar soporte de GPU
python3 -c "import torch; print(f'CUDA: {torch.cuda.is_available()}, GPU: {torch.cuda.get_device_name(0)}')"
```

### Paso 4 — Autenticación en HuggingFace

```bash
# Login para acceder a modelos con control de acceso (Llama, Gemma)
huggingface-cli login
# Ingresa tu token de HF desde https://huggingface.co/settings/tokens

# O establece la variable de entorno
export HF_TOKEN=hf_tu-token-aqui
```

### Paso 5 — Opcional: Seguimiento con Weights & Biases

```bash
# Configurar el seguimiento de experimentos (altamente recomendado)
pip install wandb
wandb login  # Ingresa tu clave API de W&B desde https://wandb.ai/settings

# O desactivar W&B
export WANDB_DISABLED=true
```

***

## Ajuste fino supervisado (SFT)

SFT es siempre el primer paso antes de cualquier técnica de RL.

### Prepara tu conjunto de datos

```python
# Formato: biblioteca datasets con columna 'messages' o 'text'
# Formato ChatML (recomendado)
from datasets import Dataset

data = [
    {
        "messages": [
            {"role": "system", "content": "Eres un asistente útil de nube GPU."},
            {"role": "user", "content": "¿Cómo alquilo una GPU en Clore.ai?"},
            {"role": "assistant", "content": "Visita clore.ai/marketplace, filtra por especificaciones de GPU, selecciona un servidor y haz clic en Rent. El acceso SSH se proporciona inmediatamente después del pago."}
        ]
    },
    # ... más ejemplos
]

dataset = Dataset.from_list(data)
dataset.save_to_disk("data/sft_dataset")
dataset.push_to_hub("tu-usuario/mi-sft-dataset")  # opcional
```

### Script de entrenamiento SFT

```python
# sft_train.py
from trl import SFTTrainer, SFTConfig
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
import torch

# Configuración del modelo
model_name = "meta-llama/Llama-3.2-8B-Instruct"

# QLoRA: configuración de cuantización a 4 bits
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# Cargar modelo
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)

# Cargar tokenizador
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

# Configuración de LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

# Cargar dataset
dataset = load_dataset("trl-lib/ultrachat_200k", split="train_sft[:10%]")

# Configuración de entrenamiento
training_config = SFTConfig(
    output_dir="./sft_output",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    warmup_ratio=0.05,
    lr_scheduler_type="cosine",
    fp16=False,
    bf16=True,
    max_seq_length=2048,
    dataset_text_field="messages",
    logging_steps=10,
    save_steps=100,
    save_total_limit=3,
    push_to_hub=False,
    report_to="wandb",  # o "none"
)

# Inicializar trainer
trainer = SFTTrainer(
    model=model,
    args=training_config,
    train_dataset=dataset,
    peft_config=lora_config,
    tokenizer=tokenizer,
)

# Entrenar
trainer.train()
trainer.save_model("./sft_final")
```

```bash
# Ejecutar entrenamiento
python3 sft_train.py
```

***

## DPO (Optimización Directa por Preferencias)

DPO es el método de alineación más popular — no se necesita modelo de recompensa, solo pares de preferencias.

### Preparar dataset para DPO

```python
# Formato: cada ejemplo tiene 'prompt', 'chosen', 'rejected'
from datasets import Dataset

data = [
    {
        "prompt": "Explica cómo optimizar la utilización de la GPU",
        "chosen": "Para optimizar la utilización de la GPU: 1) Usa tamaños de lote más grandes para maximizar la ocupación, 2) Habilita precisión mixta (bf16/fp16), 3) Perfila con nvidia-smi para identificar cuellos de botella, 4) Usa streams de CUDA para operaciones en paralelo.",
        "rejected": "Simplemente usa más GPUs."
    },
    # ... más pares de preferencias
]

dataset = Dataset.from_list(data)
```

### Script de entrenamiento DPO

```python
# dpo_train.py
from trl import DPOTrainer, DPOConfig
from transformers import AutoTokenizer, AutoModelForCausalLM
from datasets import load_dataset
import torch

model_name = "./sft_final"  # ¡Comienza desde tu modelo SFT!

# Cargar modelo SFT (la política a alinear)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

# Modelo de referencia (copia congelada del modelo SFT)
ref_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# Cargar dataset de preferencias
dataset = load_dataset("trl-lib/ultrafeedback_binarized", split="train[:5%]")

# Configuración DPO
dpo_config = DPOConfig(
    output_dir="./dpo_output",
    num_train_epochs=1,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=5e-7,           # Mucho más bajo que SFT
    beta=0.1,                     # Coeficiente de penalización KL
    loss_type="sigmoid",          # Pérdida DPO estándar
    max_length=2048,
    max_prompt_length=512,
    bf16=True,
    logging_steps=10,
    save_steps=50,
    report_to="wandb",
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=dpo_config,
    train_dataset=dataset,
    tokenizer=tokenizer,
)

trainer.train()
trainer.save_model("./dpo_final")
```

***

## PPO (Proximal Policy Optimization)

PPO es el enfoque clásico de RLHF — úsalo cuando tengas una señal de recompensa:

```python
# ppo_train.py
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
from transformers import AutoTokenizer, pipeline
from datasets import load_dataset
import torch

model_name = "./sft_final"

# Modelo de política (con cabeza de valor para PPO)
model = AutoModelForCausalLMWithValueHead.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
)

tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# Modelo de recompensa (puede ser cualquier función de puntuación)
sentiment_pipe = pipeline(
    "sentiment-analysis",
    model="distilbert/distilbert-base-uncased-finetuned-sst-2-english",
    device=0,
)

def reward_fn(texts):
    """Puntúa cada respuesta. Devuelve una lista de tensores de recompensa."""
    results = sentiment_pipe(texts)
    rewards = []
    for result in results:
        score = result["score"] if result["label"] == "POSITIVE" else -result["score"]
        rewards.append(torch.tensor(score))
    return rewards

ppo_config = PPOConfig(
    output_dir="./ppo_output",
    learning_rate=1.41e-5,
    mini_batch_size=1,
    batch_size=4,
    gradient_accumulation_steps=4,
    kl_penalty="kl",
    target_kl=6.0,
    cliprange=0.2,
    vf_coef=0.1,
)

trainer = PPOTrainer(
    config=ppo_config,
    model=model,
    ref_model=None,  # Copia automáticamente el modelo inicial como referencia
    tokenizer=tokenizer,
)

# Bucle de entrenamiento
dataset = load_dataset("imdb", split="train[:1000]")
for epoch in range(3):
    for batch in trainer.dataloader:
        queries = batch["input_ids"]
        
        # Generar respuestas
        responses = trainer.generate(queries, max_new_tokens=100)
        
        # Puntuar respuestas
        texts = tokenizer.batch_decode(responses, skip_special_tokens=True)
        rewards = reward_fn(texts)
        
        # Actualización PPO
        stats = trainer.step(queries, responses, rewards)
        trainer.log_stats(stats, batch, rewards)
```

***

## GRPO (Group Relative Policy Optimization)

GRPO se usa en DeepSeek-R1 para entrenamiento de razonamiento:

```python
# grpo_train.py
from trl import GRPOTrainer, GRPOConfig
from transformers import AutoTokenizer, AutoModelForCausalLM
from datasets import Dataset
import re, torch

model_name = "Qwen/Qwen2.5-7B-Instruct"

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Dataset de matemáticas
def make_math_dataset():
    examples = [
        {"prompt": "¿Cuánto es 2+2?", "answer": "4"},
        {"prompt": "¿Cuánto es 15 * 7?", "answer": "105"},
        # ... más problemas matemáticos
    ]
    return Dataset.from_list(examples)

dataset = make_math_dataset()

def correctness_reward(completions, answer, **kwargs):
    """Recompensa 1.0 si la respuesta es correcta, 0.0 en caso contrario."""
    rewards = []
    for completion in completions:
        # Extraer número final de la completación
        numbers = re.findall(r'\d+', completion[-1]["content"])
        if numbers and numbers[-1] == answer:
            rewards.append(1.0)
        else:
            rewards.append(0.0)
    return rewards

grpo_config = GRPOConfig(
    output_dir="./grpo_output",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    num_generations=8,       # GRPO genera G respuestas por prompt
    learning_rate=5e-7,
    bf16=True,
    logging_steps=10,
)

trainer = GRPOTrainer(
    model=model,
    args=grpo_config,
    train_dataset=dataset,
    reward_funcs=correctness_reward,
    tokenizer=tokenizer,
)

trainer.train()
```

***

## Entrenamiento multi-GPU

Usa `accelerate` para entrenamiento distribuido:

```bash
# Configurar accelerate para multi-GPU
accelerate config

# Ejemplo de configuración para 4 GPUs:
# - compute_environment: LOCAL_MACHINE
# - distributed_type: MULTI_GPU
# - num_processes: 4
# - mixed_precision: bf16

# Lanzar entrenamiento en todas las GPUs
accelerate launch sft_train.py
accelerate launch dpo_train.py

# O especificar GPUs explícitamente
CUDA_VISIBLE_DEVICES=0,1,2,3 accelerate launch \
  --num_processes 4 \
  --mixed_precision bf16 \
  sft_train.py
```

***

## Usando la CLI de TRL

TRL proporciona comandos CLI convenientes:

```bash
# SFT vía CLI
trl sft \
  --model_name_or_path meta-llama/Llama-3.2-8B-Instruct \
  --dataset_name trl-lib/ultrachat_200k \
  --dataset_text_field messages \
  --output_dir ./cli_sft_output \
  --num_train_epochs 3 \
  --per_device_train_batch_size 2 \
  --gradient_accumulation_steps 4 \
  --learning_rate 2e-4 \
  --bf16 \
  --use_peft \
  --lora_r 16 \
  --lora_alpha 32

# DPO vía CLI
trl dpo \
  --model_name_or_path ./cli_sft_output \
  --dataset_name trl-lib/ultrafeedback_binarized \
  --output_dir ./cli_dpo_output \
  --num_train_epochs 1 \
  --beta 0.1 \
  --bf16
```

***

## Monitorización del entrenamiento

```bash
# Vigilar la utilización de la GPU
watch -n 1 nvidia-smi

# Monitorizar la pérdida de entrenamiento (si usas W&B)
# Abre https://wandb.ai/tu-usuario en el navegador

# Revisa el directorio de salida para puntos de control
ls -lh sft_output/checkpoint-*/

# Reanudar desde checkpoint
python3 sft_train.py --resume_from_checkpoint sft_output/checkpoint-500/
```

***

## Recomendaciones de GPU de Clore.ai

El entrenamiento con TRL es una de las cargas de trabajo más intensivas en VRAM. Elige tu GPU según el tamaño del modelo y el método:

| Tarea                                                   | GPU                | Notas                                                                                          |
| ------------------------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------- |
| SFT / DPO en 7–8B (QLoRA)                               | **RTX 3090** 24 GB | \~8 GB para QLoRA 4-bit; cabe cómodamente; \~ $0.12/h en Clore.ai                              |
| SFT / DPO en 7–8B (LoRA bf16)                           | **RTX 4090** 24 GB | Misma VRAM que la 3090 pero 30% más rápida en cómputo; ideal para velocidad de iteración       |
| SFT completo en 7B o DPO en 13B                         | **A100 40 GB**     | 40 GB alberga entrenamiento 7B en precisión completa; la memoria ECC evita errores silenciosos |
| PPO / afinamiento completo 7B, o cualquier QLoRA de 70B | **A100 80 GB**     | PPO necesita 2× modelo de política+referencia en VRAM; 80 GB ejecuta ambos sin OOM             |

**Consejo práctico:** Comienza en RTX 3090 con QLoRA para experimentar — entrena Llama 3 8B en \~2 hrs con 10K ejemplos. Una vez validada la canalización, pasa a A100 80GB para ejecuciones en precisión completa o modelos de 70B.

**Números de velocidad (Llama 3 8B SFT, QLoRA, batch=4, seq=2048):**

* RTX 3090: \~1,100 tokens/seg de rendimiento de entrenamiento
* RTX 4090: \~1,450 tokens/seg
* A100 80GB: \~2,800 tokens/seg (bf16 completo, sin cuantización)

***

## Solución de problemas

### CUDA Out of Memory

```bash
# Reduce el tamaño del lote
per_device_train_batch_size=1
gradient_accumulation_steps=16  # Mantener el mismo tamaño efectivo de lote

# Usar cuantización de 4 bits (QLoRA)
# Añadir BitsAndBytesConfig con load_in_4bit=True

# Habilitar checkpointing de gradientes
gradient_checkpointing=True

# Reducir la longitud de la secuencia
max_seq_length=1024  # en lugar de 2048+

# Comprobar la memoria de la GPU
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
```

### La pérdida es NaN

```bash
# Causa común: tasa de aprendizaje demasiado alta
learning_rate=1e-5  # Prueba con un valor más bajo

# Causa común: datos malos (cadenas vacías, valores None)
# Validar el conjunto de datos:
python3 -c "
from datasets import load_from_disk
ds = load_from_disk('data/sft_dataset')
print(ds[0])
print(f'Length: {len(ds)}')
# Comprobar si hay None
none_count = sum(1 for x in ds if x.get('messages') is None)
print(f'None count: {none_count}')
"

# Habilitar bf16 en lugar de fp16 (más estable)
bf16=True
fp16=False
```

### DPO: `chosen_rewards > rejected_rewards` es False

```bash
# Esto significa que el modelo prefiere respuestas rechazadas — sobreajuste o datos malos
# Soluciones:
# 1. Comprueba la calidad de tu conjunto de datos
# 2. Reducir beta (menos penalización KL)
# 3. Reducir la tasa de aprendizaje
# 4. Añadir más entrenamiento SFT antes de DPO
beta=0.05  # Prueba valores más pequeños
```

### El entrenamiento es muy lento

```bash
# Habilitar Flash Attention 2
pip install flash-attn --no-build-isolation

# En tu código:
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    attn_implementation="flash_attention_2",
    torch_dtype=torch.bfloat16,
)

# Usar bf16 en lugar de fp16 en GPUs Ampere+ (A100, RTX 3000+)
bf16=True

# Aumentar los workers del DataLoader
dataloader_num_workers=4

# Comprobar si la GPU se está usando realmente
nvidia-smi  # Debería mostrar alta utilización de GPU
```

### `tokenizer.pad_token` advertencia

```bash
# Solución estándar para tokenizadores Llama/Mistral
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"  # Importante para la estabilidad del entrenamiento
```

### Permiso denegado / HuggingFace 401

```bash
# Volver a iniciar sesión
huggingface-cli login

# Establecer el token en el entorno
export HF_TOKEN=hf_your-token

# Para modelos/conjuntos de datos privados, asegura que tienes acceso:
# Ir a https://huggingface.co/meta-llama/Llama-3.2-8B-Instruct
# Hacer clic en "Request access" y aceptar la licencia
```

***

## Guardar y Compartir Tu Modelo

```bash
# Fusionar los pesos LoRA en el modelo base
python3 << 'EOF'
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.2-8B-Instruct",
    torch_dtype=torch.bfloat16,
    device_map="auto"
)
model = PeftModel.from_pretrained(base_model, "./sft_final")
merged = model.merge_and_unload()
merged.save_pretrained("./merged_model")

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-8B-Instruct")
tokenizer.save_pretrained("./merged_model")
print("¡Modelo fusionado guardado!")
EOF

# Subir a HuggingFace
huggingface-cli upload your-username/my-trl-model ./merged_model
```

***

## Enlaces Útiles

* **GitHub**: <https://github.com/huggingface/trl> ⭐ 10K+
* **Documentación**: <https://huggingface.co/docs/trl>
* **Artículo DPO**: <https://arxiv.org/abs/2305.18290>
* **GRPO / DeepSeek-R1**: <https://arxiv.org/abs/2501.12599>
* **Artículo PPO (RLHF)**: <https://arxiv.org/abs/2203.02155>
* **HuggingFace PEFT**: <https://github.com/huggingface/peft>
* **Weights & Biases**: <https://wandb.ai>
* **Flash Attention**: <https://github.com/Dao-AILab/flash-attention>
* **Clore.ai Marketplace**: <https://clore.ai/marketplace>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/entrenamiento/trl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
