Large Language Models (LLMs)
Desde BERT y GPT hasta agentes autónomos: la revolución de los grandes modelos de lenguaje. Arquitecturas encoder-only vs decoder-only, pre-training a escala, tokenización, scaling laws, fine-tuning con PEFT/LoRA, RLHF, DPO, prompting avanzado, modelos de razonamiento, LLMs multimodales, function calling y frameworks de agentes.
De Transformers a LLMs
El Transformer (Vaswani et al., 2017) introdujo una arquitectura basada enteramente en atención que reemplazó a las RNNs. Pero fue la idea de pre-entrenar masivamente un Transformer con texto no etiquetado y luego ajustarlo (fine-tuning) a tareas específicas la que dio lugar a los Large Language Models (LLMs).
Para entender cómo funcionan los mecanismos de atención y la arquitectura Transformer original, se recomienda consultar el submódulo Transformers. Aquí partimos de ese conocimiento para explicar cómo BERT y GPT sentaron las bases de lo que hoy llamamos LLMs.
El salto conceptual fue doble: por un lado, entrenar con enormes cantidades de texto sin etiquetar permitió capturar conocimiento lingüístico, factual y de razonamiento de forma no supervisada; por otro, el mecanismo de atención resultó ser extraordinariamente escalable — duplicar parámetros y datos producía mejoras predecibles y consistentes.
Dos filosofías divergentes emergieron casi simultáneamente en 2018:
BERT: Bidirectional Encoder Representations from Transformers
BERT (Devlin et al., 2018) fue el primer modelo en demostrar que un Transformer encoder pre-entrenado bidireccionalmente podía lograr resultados SOTA en 11 tareas de NLP simultáneamente, simplemente añadiendo una capa de clasificación encima.
📄 Paper: "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding"
Devlin, Chang, Lee & Toutanova (2018).
arXiv:1810.04805 ·
GitHub
Arquitectura del Encoder
BERT usa exclusivamente el encoder del Transformer original:
Objetivos de pre-entrenamiento
La clave de BERT es su bidireccionalidad: cada token puede atender a todos los demás tokens de la secuencia, tanto los que están a su izquierda como a su derecha. Esto contrasta radicalmente con los modelos autoregresivos (GPT), donde cada token solo puede «mirar» hacia la izquierda. La bidireccionalidad permite que BERT construya representaciones más ricas del contexto, lo cual es especialmente útil para tareas de comprensión del lenguaje como clasificación, NER o QA extractivo.
Se enmascara aleatoriamente el 15% de los tokens de entrada y el modelo debe predecir los tokens originales. De ese 15%:
- 80% → se reemplaza por
[MASK] - 10% → se reemplaza por un token aleatorio
- 10% → se deja sin cambiar
Esta estrategia evita que el modelo dependa del token especial [MASK]
(que nunca aparece en fine-tuning/inferencia) y fuerza representaciones robustas.
Donde \(\mathcal{M}\) es el conjunto de posiciones enmascaradas.
Dado un par de frases (A, B), el modelo predice si B es la frase siguiente real de A
(IsNext) o una frase aleatoria (NotNext). Se usa la representación
del token [CLS] para la clasificación binaria.
Nota: estudios posteriores (RoBERTa, ALBERT) demostraron que NSP tiene un impacto limitado y que eliminar este objetivo no afecta (o mejora) los resultados.
Input Representation
Cada token de entrada es la suma de tres embeddings:
| Embedding | Descripción | Ejemplo |
|---|---|---|
| Token | WordPiece embedding del subword | "play" → "play", "##ing" → "##ing" |
| Segment | Indica frase A o B (para tareas de pares) | [0, 0, 0, 1, 1, 1] |
| Position | Posición aprendida (no sinusoidal, max 512) | [0, 1, 2, ..., 511] |
Configuraciones de BERT
| Modelo | Capas (L) | Hidden (H) | Heads (A) | Parámetros |
|---|---|---|---|---|
| BERTBASE | 12 | 768 | 12 | 110M |
| BERTLARGE | 24 | 1024 | 16 | 340M |
Familia y variantes de BERT
Tras el éxito de BERT surgieron numerosas variantes que mejoran aspectos específicos:
Robustly Optimized BERT Approach. Elimina NSP, usa más datos (160 GB vs 16 GB), más batch size, secuencias más largas, y masking dinámico (diferente máscara cada epoch).
Resultados: +2-4 puntos sobre BERT en GLUE, SQuAD y RACE.
A Lite BERT. Dos técnicas de reducción de parámetros:
- Factorized Embedding: separa la dimensión del vocabulario (V×E) de la hidden (E×H)
- Cross-Layer Parameter Sharing: todas las capas comparten pesos
ALBERT-xxlarge: 235M → 12M parámetros únicos, con rendimiento similar a BERT-large.
Knowledge distillation aplicada a BERT. Un modelo "estudiante" de 6 capas que aprende de BERT-base (12 capas) como "profesor".
Resultado: 60% más rápido, 40% menos parámetros (66M), retiene el 97% del rendimiento de BERT.
En lugar de MLM, usa un esquema GAN-like: un generador pequeño reemplaza tokens y un discriminador (el modelo principal) predice qué tokens fueron reemplazados (replaced token detection).
Ventaja: cada token recibe una señal de entrenamiento (no solo el 15% enmascarado), lo que lo hace mucho más eficiente computacionalmente.
Disentangled attention: separa las representaciones de contenido y posición en la atención. Añade un Enhanced Mask Decoder que usa posición absoluta solo en la capa final.
DeBERTa-v3 fue el primer modelo en superar el rendimiento humano en el benchmark SuperGLUE.
| Modelo | Params | Mejora clave | GLUE Score | Año |
|---|---|---|---|---|
| BERT-base | 110M | MLM + NSP | 79.6 | 2018 |
| RoBERTa-large | 355M | Más datos, sin NSP | 88.5 | 2019 |
| ALBERT-xxlarge | 235M | Parameter sharing | 89.4 | 2019 |
| DistilBERT | 66M | Distillation | 77.0 | 2019 |
| ELECTRA-large | 335M | Replaced token detection | 89.5 | 2020 |
| DeBERTa-v3 | 304M | Disentangled attention | 91.4 | 2020 |
Fine-tuning de BERT
El paradigma "pre-train then fine-tune" de BERT revolucionó el NLP. La idea central es simple: tomar el encoder pre-entrenado y añadir una capa de clasificación específica para cada tarea.
# ═══════════════════════════════════════════════════
# Fine-tuning BERT para clasificación de sentimiento
# ═══════════════════════════════════════════════════
from transformers import BertTokenizer, BertForSequenceClassification
from transformers import Trainer, TrainingArguments
from datasets import load_dataset
# 1. Cargar modelo pre-entrenado
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=2 # positivo / negativo
)
# 2. Dataset
dataset = load_dataset('imdb')
def tokenize(batch):
return tokenizer(batch['text'], padding='max_length',
truncation=True, max_length=512)
tokenized = dataset.map(tokenize, batched=True)
# 3. Training
args = TrainingArguments(
output_dir='./results',
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
learning_rate=2e-5, # LR bajo, típico de fine-tuning
weight_decay=0.01,
evaluation_strategy='epoch',
warmup_steps=500,
fp16=True, # Mixed precision
)
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized['train'],
eval_dataset=tokenized['test'],
)
trainer.train() # ~93% accuracy en IMDb
GPT: Generative Pre-trained Transformer
GPT (Radford et al., 2018) tomó el camino opuesto a BERT: usar solo el decoder del Transformer con atención causal (masked) para modelar la probabilidad del siguiente token.
📄 Papers de la serie GPT
- GPT-1: "Improving Language Understanding by Generative Pre-Training" (Radford et al., 2018) [PDF]
- GPT-2: "Language Models are Unsupervised Multitask Learners" (Radford et al., 2019) [PDF] · GitHub
- GPT-3: "Language Models are Few-Shot Learners" (Brown et al., 2020) arXiv:2005.14165
- GPT-4: "GPT-4 Technical Report" (OpenAI, 2023) arXiv:2303.08774
Modelado autoregresivo
GPT modela la distribución de probabilidad del lenguaje de forma causal (izquierda a derecha):
El objetivo de pre-entrenamiento es maximizar la log-verosimilitud:
La máscara causal impide que cada token atienda a tokens futuros, lo que permite la generación autoregresiva token a token.
Aunque esta restricción puede parecer una limitación (se pierde contexto futuro), en la práctica resulta ser una ventaja enorme para la generación: el modelo aprende a predecir el siguiente token de forma muy precisa, y al encadenar predicciones, puede producir texto coherente, código funcional, razonamiento matemático y mucho más. Además, esta formulación permite un entrenamiento muy eficiente al calcular la loss para todos los tokens de una secuencia en paralelo durante el training (teacher forcing), a diferencia de las RNNs que requerían procesamiento secuencial.
Evolución de GPT
| Modelo | Params | Datos | Context | Innovación clave |
|---|---|---|---|---|
| GPT-1 | 117M | BookCorpus (5 GB) | 512 | Pre-train + fine-tune con decoder-only |
| GPT-2 | 1.5B | WebText (40 GB) | 1024 | "Zero-shot learning" sin fine-tuning |
| GPT-3 | 175B | 300B tokens | 2048 | In-context learning, few-shot |
| GPT-3.5 | ~175B | Idem + RLHF | 4096 | InstructGPT → ChatGPT |
| GPT-4 | ~1.8T* | ~13T tokens* | 8K-128K | MoE, multimodal, razonamiento |
| GPT-4o | — | — | 128K | Natively multimodal (texto+imagen+audio) |
* Estimaciones no oficiales (fuentes: análisis independientes, filtraciones).
BERT vs GPT: Comparativa
| Aspecto | BERT (Encoder-only) | GPT (Decoder-only) |
|---|---|---|
| Dirección | Bidireccional | Unidireccional (izq → der) |
| Pre-training | MLM + NSP | Causal Language Modeling |
| Uso principal | Comprensión (clasificación, NER, QA) | Generación (texto, código, chat) |
| Fine-tuning | Necesario para cada tarea | Opcional (in-context learning) |
| Contexto | Ventana completa (fija, 512) | Ventana creciente (hasta 128K+) |
| Atención | Full attention (O(n²)) | Causal mask (triangular inferior) |
| Escalado | ~340M max (limitado) | ~1.8T (escala masivamente) |
| Embeddings | Token + Segment + Position (aprendido) | Token + Position (aprendido/RoPE) |
| Normalización | Post-LayerNorm | Pre-LayerNorm (GPT-2+) → RMSNorm |
| Activación FFN | GELU | GELU → SwiGLU (LLaMA+) |
| Ventaja | Contexto bidireccional rico | Genera texto, escala mejor |
Este dominio de los modelos decoder-only no significa que los encoders hayan desaparecido. En aplicaciones como búsqueda semántica, recuperación de documentos y embeddings, los modelos tipo BERT (y sus sucesores como GTE, E5 o Nomic-Embed) siguen siendo la opción preferida por su capacidad de generar representaciones densas bidireccionales de alta calidad. De hecho, muchos sistemas RAG combinan un encoder para la recuperación con un decoder para la generación.
🔬 Comparador de tamaño de modelos
Visualiza la escala de diferentes LLMs en términos de parámetros y datos de entrenamiento.
El ecosistema open-source
A partir de 2023, una ola de modelos open-source / open-weight democratizó el acceso a LLMs de alta calidad:
| Modelo | Organización | Params | Licencia | Innovación |
|---|---|---|---|---|
| LLaMA | Meta | 7-65B | Research | Modelos fundacionales eficientes |
| LLaMA 2 | Meta | 7-70B | Comercial | Datos 40% más, GQA en 70B |
| LLaMA 3 | Meta | 8-405B | Comercial | 15T tokens, 128K context |
| Mistral 7B | Mistral | 7B | Apache 2.0 | Sliding Window Attention, GQA |
| Mixtral 8×7B | Mistral | 46.7B (12.9B activos) | Apache 2.0 | Mixture of Experts (MoE) |
| Qwen 2.5 | Alibaba | 0.5-72B | Apache 2.0 | Multilingüe, código, matemáticas |
| Gemma 2 | 2-27B | Abierta | Destilación de Gemini | |
| DeepSeek-V3 | DeepSeek | 671B (37B activos) | MIT | MoE eficiente, FP8 training |
| Phi-3 | Microsoft | 3.8B | MIT | Datos curados de alta calidad |
📚 Repositorios clave
- meta-llama/llama3 — Modelo LLaMA 3
- mistralai/mistral-src — Modelo Mistral
- HuggingFace Model Hub — Miles de modelos open-source
- huggingface/transformers — Librería unificada para LLMs
- vllm-project/vllm — Inferencia eficiente para LLMs
Pre-training de LLMs: el cimiento
El pre-training es la fase donde un LLM aprende la estructura del lenguaje, conocimiento factual y capacidad de razonamiento a partir de billones de tokens de texto no etiquetado. Es la fase más costosa computacionalmente (millones de dólares en GPUs durante semanas o meses).
Datasets de pre-training
La calidad y diversidad de los datos determina en gran medida las capacidades del modelo. Los datasets modernos combinan múltiples fuentes:
| Dataset | Tamaño | Fuentes | Usado por |
|---|---|---|---|
| Common Crawl | ~250B páginas | Web crawl global | GPT-3, LLaMA, todos |
| C4 | ~750 GB | Common Crawl filtrado | T5, PaLM |
| The Pile | 825 GB | 22 fuentes diversas | GPT-NeoX, Pythia |
| RedPajama | 1.2T tokens | Réplica de LLaMA | Open-source |
| FineWeb | 15T tokens | Common Crawl curado por HF | Open-source |
| The Stack v2 | ~4TB | Código fuente (GitHub) | StarCoder 2 |
| Wikipedia | ~20 GB | Enciclopedia | BERT, GPT-3, todos |
| Books3 | ~100 GB | Libros | GPT-3, LLaMA (controversia) |
| arXiv | ~85 GB | Papers científicos | Galactica, LLaMA |
La elección y curado de datos se ha convertido en uno de los factores más determinantes del rendimiento final de un LLM. Modelos como Phi-3 (Microsoft) demostraron que un modelo pequeño (3.8B) entrenado con datos cuidadosamente seleccionados puede competir con modelos 10× mayores entrenados con datos menos curados. El dicho en la comunidad es claro: «data quality trumps data quantity».
Además, la composición del dataset (data mix) — qué proporción de cada fuente incluir — es un hiperparámetro crucial que se ajusta empíricamente. Por ejemplo, incluir más código mejora el razonamiento lógico incluso en tareas de lenguaje natural (Muennighoff et al., 2023).
Ref: Penedo et al. (2024) "The FineWeb Datasets" (arXiv:2406.17557)
Tokenización para LLMs
Los LLMs no procesan texto crudo sino tokens — unidades subword que equilibran la granularidad entre caracteres y palabras.
import tiktoken
# GPT-4 tokenizer
enc = tiktoken.get_encoding("cl100k_base")
text = "Los Large Language Models procesan tokens, no palabras."
tokens = enc.encode(text)
print(f"Texto: {text}")
print(f"Tokens: {tokens}")
print(f"Nº tokens: {len(tokens)}")
print(f"Decodificado: {[enc.decode([t]) for t in tokens]}")
# Output:
# Texto: Los Large Language Models procesan tokens, no palabras.
# Tokens: [30832, 20902, 11688, 27972, 62853, 11, 912, 84113, 13]
# Nº tokens: 9
# Decodificado: ['Los', ' Large', ' Language', ' Models', ' procesan', ' tokens', ',', ' no', ' palabras', '.']
Scaling Laws: las leyes del escalado
La tokenización tiene un impacto directo en la eficiencia del modelo en diferentes idiomas: un texto en español típicamente requiere un 20-30% más tokens que el mismo contenido en inglés con tokenizadores entrenados mayoritariamente en inglés. Esto significa que el modelo necesita más contexto (y compute) para procesar la misma información. Modelos como Qwen y Gemma, con vocabularios grandes (150K+) y entrenamiento multilingüe explícito, mitigan este problema.
Un descubrimiento fundamental de la era LLM es que el rendimiento del modelo sigue leyes de potencia predecibles con respecto al tamaño del modelo, la cantidad de datos y el compute total.
📄 Papers fundamentales
- Kaplan et al. (2020): "Scaling Laws for Neural Language Models" arXiv:2001.08361
- Hoffmann et al. (2022): "Training Compute-Optimal Large Language Models" (Chinchilla) arXiv:2203.15556
Leyes de Kaplan (OpenAI, 2020)
La loss del modelo sigue una ley de potencia con tres variables:
Donde \(N\) = parámetros, \(D\) = tokens, \(C\) = FLOPs. Los exponentes encontrados: \(\alpha_N \approx 0.076\), \(\alpha_D \approx 0.095\), \(\alpha_C \approx 0.050\).
Chinchilla: ratio óptimo de datos/modelo
Es decir, un modelo de 10B parámetros debería entrenarse con ~200B tokens. GPT-3 (175B params, 300B tokens) estaba sub-entrenado según esta regla.
| Modelo | Params N | Tokens D | Ratio D/N | ¿Chinchilla-optimal? |
|---|---|---|---|---|
| GPT-3 | 175B | 300B | 1.7× | ❌ Sub-trained |
| Chinchilla | 70B | 1.4T | 20× | ✅ Optimal |
| LLaMA | 7B | 1T | 143× | ✅ Over-trained (deliberado) |
| LLaMA 2 | 70B | 2T | 28.5× | ≈ Sí |
| LLaMA 3 | 8B | 15T | 1875× | ✅ "Over-trained" para eficiencia en inferencia |
💡 Tendencia post-Chinchilla
Modelos recientes (LLaMA 3, Phi-3) intencionalmente sobre-entrenan modelos pequeños con muchos más datos de lo óptimo según Chinchilla. ¿Por qué? Porque el coste de inferencia (que depende de N) domina al de training. Un modelo de 8B con 15T tokens rinde como uno de 70B pero es 10× más barato de servir.
Esta tensión entre coste de entrenamiento y coste de inferencia ha reconfigurado el campo. Las scaling laws originales de Kaplan optimizaban el training; las de Chinchilla equilibraban datos y modelo; y la práctica actual de LLaMA 3, Phi y Gemma prioriza la eficiencia en inferencia, sobre-entrenando deliberadamente para obtener modelos pequeños pero competitivos. El resultado es un ecosistema donde modelos de 8B parámetros ejecutables en hardware consumer pueden alcanzar rendimientos notables.
Arquitectura moderna de LLMs
Los LLMs modernos (LLaMA, Mistral, Qwen, etc.) han convergido en un conjunto de mejoras arquitectónicas respecto al Transformer original:
| Componente | Transformer original | LLMs modernos | Beneficio |
|---|---|---|---|
| Normalización | Post-LayerNorm | Pre-RMSNorm | Training más estable |
| Pos. Encoding | Sinusoidal | RoPE (rotary) | Generalización a contextos largos |
| Activación FFN | ReLU/GELU | SwiGLU | +1-2% quality |
| Attention | Multi-Head (MHA) | GQA / MQA | KV-cache 8× más pequeño |
| FFN ratio | 4×d | 8/3×d (SwiGLU) | Más eficiente |
| Bias | Sí (en Linear) | No (sin bias) | Menos params, misma calidad |
| Tying | A veces | Token embedding = LM head | Reduce params |
| Context | 512-2048 | 8K-1M+ (RoPE + YaRN/NTK) | Documentos largos |
RoPE (Su et al., 2021) codifica posición rotando los vectores query y key en el espacio complejo:
Propiedad clave: el producto punto entre queries/keys solo depende de la distancia relativa entre posiciones, no de la absoluta. Esto permite la extensión de contexto (ej: entrenar en 4K pero inferir en 128K con interpolación NTK/YaRN).
En Multi-Head Attention (MHA), cada cabeza tiene su propio K y V, lo que genera un KV-cache enorme. GQA (Ainslie et al., 2023) agrupa varias cabezas de query para compartir un mismo par K/V.
- MHA: h cabezas, h K/V → KV-cache = 2 × h × d × seq_len
- GQA: h cabezas query, g grupos K/V (g < h) → KV-cache / (h/g)×
- MQA: caso extremo con g=1 (un único K/V para todas las cabezas)
LLaMA 2 70B, LLaMA 3, Mistral: usan GQA con 8 grupos KV.
Propuesta por Shazeer (2020), SwiGLU combina Swish con un mecanismo de gating:
Donde \(\text{Swish}(x) = x \cdot \sigma(\beta x)\). El FFN con SwiGLU usa dimensión \(\frac{8}{3}d\) (vs 4d con ReLU) para igualar el número de parámetros.
Infraestructura de pre-training
Entrenar un LLM a escala requiere paralelismo masivo en miles de GPUs. Las técnicas principales son:
| Modelo | GPUs | Tiempo | Coste estimado | Framework |
|---|---|---|---|---|
| GPT-3 (175B) | 10,000 V100 | ~14 días | ~$4.6M | Custom (Megatron-LM) |
| LLaMA (65B) | 2,048 A100 80GB | 21 días | ~$2.4M | Custom |
| LLaMA 3 (405B) | 16,384 H100 | ~54 días | ~$100M+ | Custom (fairscale) |
| DeepSeek-V3 | 2,048 H800 | ~2 meses | ~$5.6M | HAI-LLM (custom) |
| Mistral 7B | ~512 A100 | ~días | ~$500K | Megatron-LM |
🔧 Frameworks de training distribuido
- Megatron-LM (NVIDIA) — Estándar para 3D parallelism
- DeepSpeed (Microsoft) — ZeRO optimizer, pipeline parallelism
- PyTorch FSDP — Fully Sharded Data Parallel nativo
- JAX + XLA — TPU-native, usado por Google (PaLM, Gemini)
| Técnica | Descripción | Ahorro |
|---|---|---|
| Mixed Precision (BF16) | Forward/backward en BFloat16, master weights en FP32 | 2× memoria, ~1.5× velocidad |
| Gradient Checkpointing | Re-computa activaciones en backward en lugar de almacenarlas | ~60% memoria, +30% tiempo |
| Flash Attention 2 | Kernel fusionado GPU para attention, tiling I/O-aware | 2-4× más rápido, O(n) memoria |
| Gradient Accumulation | Acumula gradientes sobre micro-batches | Permite batches grandes con poca memoria |
| FP8 training | Forward en FP8 (DeepSeek-V3, Hopper GPUs) | 2× vs BF16 |
| ZeRO Stage 3 | Fragmenta params, gradients y optimizer state entre GPUs | N× memoria (N = nº GPUs) |
Ref: Dao (2023) "FlashAttention-2" (arXiv:2307.08691); Rajbhandari et al. (2020) "ZeRO" (arXiv:1910.02054)
🧮 Estimador de coste de pre-training
Estima el coste aproximado de pre-entrenar un LLM con la regla de 6ND FLOPs.
Mixture of Experts (MoE)
Los modelos MoE escalan el número de parámetros sin incrementar proporcionalmente el coste computacional. Solo un subconjunto de "expertos" se activa para cada token.
| Modelo MoE | Total params | Params activos | Expertos | Top-K |
|---|---|---|---|---|
| Mixtral 8×7B | 46.7B | 12.9B | 8 | 2 |
| GPT-4 (estimado) | ~1.8T | ~280B | 16 | 2 |
| DeepSeek-V3 | 671B | 37B | 256 | 8 |
| DBRX | 132B | 36B | 16 | 4 |
| Grok-1 | 314B | ~80B | 8 | 2 |
📄 Papers de MoE
- Shazeer et al. (2017): "Outrageously Large Neural Networks: The Sparsely-Gated MoE Layer" arXiv:1701.06538
- Fedus et al. (2022): "Switch Transformers: Scaling to Trillion Parameter Models" arXiv:2101.03961
- Jiang et al. (2024): "Mixtral of Experts" arXiv:2401.04088
De modelo base a chatbot: las 3 fases
Un LLM pre-entrenado (modelo "base") solo sabe completar texto. Para convertirlo en un asistente útil que siga instrucciones se necesitan tres fases adicionales:
Supervised Fine-Tuning (SFT)
El SFT adapta el modelo base a seguir instrucciones entrenándolo con pares (instrucción, respuesta) escritos por humanos o generados sintéticamente.
Este paso es crítico porque un modelo base — por muy capaz que sea — solo sabe completar texto: si le preguntas algo, puede continuar con otra pregunta en lugar de responder. El SFT enseña al modelo el formato conversacional (roles de sistema, usuario y asistente) y le muestra cómo estructurar respuestas útiles. Sin embargo, el SFT solo enseña qué responder, no cómo de bien hacerlo — para eso se necesita la fase de alignment (RLHF/DPO).
Formato de datos SFT
# Formato típico de datos SFT (chat template)
data = [
{
"messages": [
{"role": "system", "content": "Eres un asistente experto en ML."},
{"role": "user", "content": "¿Qué es el dropout?"},
{"role": "assistant", "content": "El dropout es una técnica de regularización..."}
]
},
{
"messages": [
{"role": "user", "content": "Escribe una función que ordene una lista en Python."},
{"role": "assistant", "content": "def sort_list(lst):\n return sorted(lst)"}
]
}
]
# La loss se calcula SOLO sobre los tokens del assistant (no user/system)
| Dataset SFT | Tamaño | Fuente | Notas |
|---|---|---|---|
| InstructGPT data | ~13K prompts | Humanos (labelers) | Dataset original de OpenAI |
| FLAN v2 | ~1.8K tareas / 15M ej. | Datasets NLP reformateados | Google, instruction tuning |
| ShareGPT | ~90K conversaciones | Conversaciones con ChatGPT | Usado por Vicuna |
| OpenAssistant | ~160K mensajes | Crowdsourcing | Multilingüe, open-source |
| UltraChat | ~1.5M diálogos | Sintéticos (GPT-3.5/4) | Usado por Zephyr |
| Alpaca | 52K | Sintéticos (text-davinci-003) | Self-instruct de Stanford |
Fine-tuning eficiente: PEFT
Fine-tunear todos los parámetros de un LLM de 70B requiere enormes recursos. PEFT (Parameter-Efficient Fine-Tuning) congela la mayoría del modelo y solo entrena un número reducido de parámetros.
LoRA en detalle
LoRA (Hu et al., 2021) es la técnica PEFT más utilizada. En lugar de actualizar la matriz de pesos \(W \in \mathbb{R}^{d \times k}\), LoRA añade una descomposición de bajo rango:
Con \(r = 16\), una capa de \(4096 \times 4096\) pasa de 16.7M a solo 131K parámetros entrenables (0.8%). El scaling factor \(\alpha / r\) controla la magnitud de la adaptación.
La intuición detrás de LoRA es que los cambios necesarios para adaptar un LLM a una tarea específica viven en un subespacio de baja dimensión: no es necesario modificar todos los pesos, sino solo unas pocas direcciones relevantes. Esto explica por qué con solo el 0.1-1% de parámetros entrenables se obtienen resultados comparables al fine-tuning completo. Además, los adaptadores LoRA se pueden combinar (merge) con el modelo base sin coste adicional en inferencia.
# ═══════════════════════════════════════════════════
# Fine-tuning con LoRA (PEFT library)
# ═══════════════════════════════════════════════════
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
from datasets import load_dataset
# 1. Modelo base
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto",
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
# 2. Configurar LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # Rango de la descomposición
lora_alpha=32, # Scaling factor
lora_dropout=0.05,
target_modules=[ # Capas donde aplicar LoRA
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# Output: trainable params: 13,631,488 || all params: 6,751,367,168 || 0.20%
# 3. Dataset
dataset = load_dataset("mlabonne/guanaco-llama2-1k")
# 4. Training
training_args = TrainingArguments(
output_dir="./llama2-lora",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_strategy="epoch",
)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
dataset_text_field="text",
max_seq_length=1024,
)
trainer.train()
📄 Papers de PEFT
- LoRA: Hu et al. (2021) "LoRA: Low-Rank Adaptation of Large Language Models" arXiv:2106.09685
- QLoRA: Dettmers et al. (2023) "QLoRA: Efficient Finetuning of Quantized LLMs" arXiv:2305.14314
- Prefix Tuning: Li & Liang (2021) arXiv:2101.00190
- PEFT Library: huggingface/peft
RLHF: Reinforcement Learning from Human Feedback
RLHF es el proceso que alinea un LLM con las preferencias humanas. Fue la técnica clave detrás de ChatGPT e InstructGPT.
El objetivo de RLHF
Donde \(\pi_\theta\) es la policy (el LLM), \(R_\phi\) es el reward model entrenado con preferencias humanas, y el término KL previene que el modelo se desvíe demasiado del modelo SFT (evitando reward hacking).
En la práctica, RLHF presenta varios desafíos: entrenar el reward model requiere grandes cantidades de comparaciones humanas (costosas y subjetivas), PPO es notoriamente inestable y requiere un cuidadoso ajuste de hiperparámetros, y el proceso necesita mantener cuatro modelos en GPU simultáneamente (policy, reference, reward model y value function), lo que multiplica los requisitos de memoria. Estas dificultades motivaron el desarrollo de alternativas más simples como DPO.
El reward model se entrena con la loss de Bradley-Terry sobre pares de comparación humana:
Donde \(y_w\) es la respuesta preferida y \(y_l\) la rechazada. El reward model suele ser un LLM más pequeño (ej: 6B) con una cabeza escalar.
📄 Papers de RLHF
- InstructGPT: Ouyang et al. (2022) "Training language models to follow instructions with human feedback" arXiv:2203.02155
- RLHF original: Christiano et al. (2017) "Deep RL from Human Preferences" arXiv:1706.03741
- PPO: Schulman et al. (2017) "Proximal Policy Optimization" arXiv:1707.06347
- Anthropic RLHF: Bai et al. (2022) "Training a Helpful and Harmless Assistant" arXiv:2204.05862
DPO y alternativas a RLHF
RLHF requiere entrenar un reward model separado y usar PPO (inestable, costoso). DPO simplifica esto drásticamente.
DPO: Direct Preference Optimization
Rafailov et al. (2023) demostraron que se puede optimizar directamente la policy con datos de preferencias, sin un reward model explícito:
Equivalente a RLHF pero con una simple loss de clasificación binaria. No necesita sampling, es estable, y es 3-10× más barato que PPO.
El impacto de DPO ha sido enorme: prácticamente todos los modelos open-source recientes (Zephyr, Mistral-Instruct, LLaMA 3-Instruct) usan DPO o variantes en lugar de PPO para la fase de alignment. Esto ha democratizado la creación de modelos alineados, ya que cualquiera con acceso a un dataset de preferencias puede aplicar DPO con las mismas herramientas de fine-tuning estándar (la librería TRL de Hugging Face lo soporta nativamente).
Un caso notable es GRPO (Group Relative Policy Optimization), introducido por DeepSeek, que combina ideas de DPO con RL online: genera múltiples respuestas, las puntua con un reward basado en reglas (verificación automática para matemáticas y código) y actualiza la policy sin necesidad de un reward model explícito. Esta técnica fue clave para entrenar DeepSeek-R1 y los modelos razonadores que veremos más adelante.
| Método | Reward Model | Sampling | Estabilidad | Coste | Paper |
|---|---|---|---|---|---|
| RLHF (PPO) | Sí | Sí (online) | Inestable | Alto (4 modelos en GPU) | 2203.02155 |
| DPO | No (implícito) | No (offline) | Estable | Bajo | 2305.18290 |
| KTO | No | No | Estable | Bajo | 2402.01306 |
| IPO | No | No | Estable | Bajo | 2310.12036 |
| ORPO | No | No | Estable | Muy bajo (SFT+align juntos) | 2403.07691 |
| GRPO | No (rule-based) | Sí (online) | Estable | Medio | 2402.03300 (DeepSeek) |
# ═══════════════════════════════════════════════════
# DPO Training con TRL (Transformer Reinforcement Learning)
# ═══════════════════════════════════════════════════
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import DPOTrainer, DPOConfig
from datasets import load_dataset
from peft import LoraConfig
# 1. Modelo (SFT ya hecho)
model = AutoModelForCausalLM.from_pretrained(
"my-sft-model",
torch_dtype="auto",
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("my-sft-model")
# 2. Dataset de preferencias
# Cada ejemplo: { "prompt": ..., "chosen": ..., "rejected": ... }
dataset = load_dataset("argilla/ultrafeedback-binarized-preferences")
# 3. LoRA para eficiencia
peft_config = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj"],
)
# 4. DPO Training
dpo_config = DPOConfig(
output_dir="./dpo-model",
beta=0.1, # Temperatura (controla desviación de ref)
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=5e-7, # LR muy bajo para alignment
num_train_epochs=1,
fp16=True,
)
trainer = DPOTrainer(
model=model,
ref_model=None, # None → usa modelo inicial como referencia
args=dpo_config,
train_dataset=dataset["train"],
tokenizer=tokenizer,
peft_config=peft_config,
)
trainer.train()
Datasets de alignment (preferencias)
Los datasets de preferencias son la materia prima del RLHF/DPO:
| Dataset | Tamaño | Tipo | Notas |
|---|---|---|---|
| HH-RLHF | 170K | Comparaciones | Anthropic. Helpfulness + Harmlessness |
| UltraFeedback | 64K | Rankings (4 respuestas) | 4 modelos, GPT-4 como juez |
| Nectar | 183K | Rankings (7 respuestas) | Starling-LM, GPT-4 ranking |
| Chatbot Arena | ~1M+ votos | Comparaciones humanas | LMSYS. ELO ranking real |
| OASST2 | ~24K rankings | Rankings humanos | Open Assistant, crowdsource |
Ingeniería de Prompts
Prompt engineering es el arte de diseñar las entradas (prompts) al LLM para obtener las respuestas deseadas sin modificar los pesos del modelo. Con LLMs suficientemente grandes (≥ 100B params), el prompting puede reemplazar al fine-tuning para muchas tareas.
El prompting funciona porque durante el pre-entrenamiento el LLM ha visto millones de ejemplos de «tareas implícitas» en el texto: preguntas seguidas de respuestas, traducciones, explicaciones, código con comentarios, etc. Al presentar un prompt estructurado de forma similar, el modelo «reconoce» el patrón y lo completa. Esto se conoce como in-context learning y sigue siendo un fenómeno parcialmente misterioso: no se actualizan pesos, pero el modelo se comporta como si hubiese sido entrenado para esa tarea específica.
Zero-shot y Few-shot Prompting
Zero-shot
El modelo recibe solo la instrucción, sin ejemplos. Funciona sorprendentemente bien con modelos grandes (GPT-3+), especialmente tras SFT/RLHF.
# Zero-shot: solo instrucción
prompt = """Clasifica el siguiente texto como positivo, negativo o neutral.
Texto: "La película fue entretenida pero el final me decepcionó."
Clasificación:"""
# → "Neutral" o "Mixto"
Few-shot (In-Context Learning)
Proporcionar K ejemplos (típicamente 3-8) en el prompt permite al modelo "aprender" el formato y la tarea sin actualizar pesos. Este fenómeno, descubierto con GPT-3, se llama in-context learning (ICL).
# Few-shot: 3 ejemplos + query
prompt = """Clasifica el sentimiento del texto.
Texto: "Me encanta este restaurante, la comida es increíble."
Sentimiento: Positivo
Texto: "El servicio fue pésimo y tardaron una hora."
Sentimiento: Negativo
Texto: "El lugar está bien, nada especial."
Sentimiento: Neutral
Texto: "La película fue entretenida pero el final me decepcionó."
Sentimiento:"""
# → "Mixto" o "Neutral"
📄 Paper clave
Brown et al. (2020) "Language Models are Few-Shot Learners" demostró que GPT-3 con few-shot prompting podía igualar modelos fine-tuneados en muchas tareas. arXiv:2005.14165
Investigaciones posteriores han mostrado que el in-context learning es sorprendentemente robusto: funciona incluso con etiquetas aleatorias en los ejemplos (lo que sugiere que el modelo aprende más el formato que las correspondencias específicas). Sin embargo, la calidad y diversidad de los ejemplos sí importa para tareas complejas. Min et al. (2022) ofrecen un análisis detallado de qué aspectos del in-context learning realmente contribuyen al rendimiento.
Chain-of-Thought (CoT) Prompting
CoT (Wei et al., 2022) mejora dramáticamente el rendimiento en tareas de razonamiento al pedir al modelo que muestre su proceso paso a paso.
# ❌ Sin CoT (directo) — falla en problemas complejos
prompt_directo = """Si tengo 3 manzanas y le doy la mitad a Juan,
luego compro 5 más y le doy 2 a María, ¿cuántas tengo?
Respuesta:"""
# → A veces responde mal
# ✅ Con CoT — resuelve correctamente
prompt_cot = """Si tengo 3 manzanas y le doy la mitad a Juan,
luego compro 5 más y le doy 2 a María, ¿cuántas tengo?
Pensemos paso a paso:
1. Empiezo con 3 manzanas
2. Le doy la mitad a Juan: 3/2 = 1.5, pero como son manzanas enteras,
le doy 1 (me quedo con 2) o redondeamos (1.5)
3. Compro 5 más: 1.5 + 5 = 6.5
4. Le doy 2 a María: 6.5 - 2 = 4.5
Respuesta: 4.5 manzanas
(O con enteros: 2 + 5 - 2 = 5 manzanas)"""
# ✅ Zero-shot CoT — más simple
prompt_zero_cot = """Si tengo 3 manzanas y le doy la mitad a Juan,
luego compro 5 más y le doy 2 a María, ¿cuántas tengo?
Let's think step by step."""
📄 Papers de CoT
- CoT: Wei et al. (2022) "Chain-of-Thought Prompting Elicits Reasoning in LLMs" arXiv:2201.11903
- Zero-shot CoT: Kojima et al. (2022) "Large Language Models are Zero-Shot Reasoners" arXiv:2205.11916
- Self-Consistency: Wang et al. (2022) arXiv:2203.11171
¿Por qué funciona CoT? La hipótesis principal es que los LLMs tienen capacidades latentes de razonamiento que solo se activan cuando se les da «espacio para pensar». Al generar tokens intermedios de razonamiento, el modelo puede descomponer un problema complejo en subproblemas más sencillos, cada uno de los cuales está dentro de su capacidad. Es análogo a cómo los humanos resuelven problemas paso a paso en lugar de intentar dar la respuesta directamente. Esta idea se llevó al extremo con los modelos razonadores (o1, DeepSeek-R1) que veremos en la sección de modelos razonadores.
Técnicas avanzadas de prompting
ToT (Yao et al., 2023) generaliza CoT permitiendo al modelo explorar múltiples caminos de razonamiento como un árbol, evaluar cada rama y hacer backtracking. Útil para problemas que requieren planificación (puzzles, código).
ReAct (Yao et al., 2022) intercala pasos de razonamiento (Thought) con acciones (Action: buscar en Wikipedia, ejecutar código, etc.) y observaciones (Observation: resultado de la acción).
Patrón: Thought → Action → Observation → Thought → ... → Answer
RAG (Lewis et al., 2020) complementa el conocimiento del LLM con información recuperada de una base de documentos. Pipeline:
De entre las técnicas avanzadas, RAG es posiblemente la más utilizada en producción. La razón es práctica: los LLMs tienen un conocimiento estático (cortado en la fecha de entrenamiento) y pueden alucinar información. RAG soluciona ambos problemas al proporcionar contexto actualizado y verificable en cada consulta. Combinado con modelos de embeddings como los mencionados en la sección de BERT vs GPT, RAG se ha convertido en el patrón estándar para chatbots empresariales, asistentes de documentación y sistemas de atención al cliente.
Forzar al LLM a generar salida estructurada (JSON, XML, tablas) mediante:
- Prompting: "Responde en formato JSON con los campos: ..."
- Function calling: schema JSON como herramienta (OpenAI, Anthropic)
- Constrained decoding: gramáticas formales (Outlines, Guidance, LMQL)
| Técnica | Cuándo usar | Mejora | Coste extra |
|---|---|---|---|
| Zero-shot | Tareas simples, modelos grandes | Baseline | Ninguno |
| Few-shot | Formato específico, tareas de nicho | +5-15% | Tokens de contexto |
| CoT | Matemáticas, lógica, razonamiento | +10-40% | Más tokens de output |
| Self-Consistency | Problemas con respuesta única | +5-15% vs CoT | N× llamadas |
| ToT | Puzzles, planificación, código | Variable | Alto (exploración) |
| RAG | Conocimiento actualizado, dominio específico | Elimina alucinaciones | Infra de retrieval |
| ReAct | Tareas que requieren herramientas | Acceso a info real | Llamadas a APIs |
🛠️ Constructor de prompt
Selecciona los componentes y genera un prompt template optimizado.
Modelos razonadores: una nueva frontera
Los modelos razonadores representan un salto conceptual: en lugar de generar una respuesta directamente, el modelo "piensa" internamente durante un tiempo variable antes de responder. Usan más compute en tiempo de inferencia (test-time compute) para resolver problemas más difíciles.
Test-Time Compute: el concepto clave
Test-time compute (TTC) se refiere al compute adicional que un modelo usa durante la inferencia para mejorar su respuesta. Hay dos estrategias principales:
📄 Paper clave
Snell et al. (2024) "Scaling LLM Test-Time Compute Optimally Can Be More Effective Than Scaling Model Parameters". Demostraron que un modelo pequeño (1-3B) con suficiente test-time compute puede superar a un modelo 14× más grande con respuesta directa. arXiv:2408.03314
Antes, solo se escalaba \(N\) (más parámetros). Ahora se puede escalar \(C_{\text{inference}}\) (más tokens de pensamiento, más candidatos) para obtener mejores resultados.
Esto abre un abanico de posibilidades fascinantes: un mismo modelo puede ser «rápido y barato» para preguntas sencillas (pocos tokens de pensamiento) o «lento y potente» para problemas difíciles (muchos tokens de pensamiento). La decisión de cuánto compute asignar puede ser automática (el modelo aprende a pensar más cuando detecta dificultad) o controlada por el usuario (parámetro de «esfuerzo» en modelos como o3-mini). Este paradigma recuerda al System 1 vs System 2 de Kahneman: pensamiento rápido e intuitivo frente a razonamiento lento y deliberado.
OpenAI o1 / o3: la serie de razonamiento
En septiembre de 2024, OpenAI lanzó o1-preview, el primer modelo razonador comercial. Usa chain-of-thought interna (no visible al usuario) entrenada con reinforcement learning a gran escala.
| Modelo | Fecha | Capacidad clave | Benchmarks notables |
|---|---|---|---|
| o1-preview | Sep 2024 | CoT interna, RL | AIME: 83.3%, Codeforces: 89th %ile |
| o1 | Dic 2024 | Más training, herramientas | AIME: 96.4%, GPQA: 78.8% |
| o3-mini | Ene 2025 | Eficiente, ajuste de esfuerzo | Codeforces: 96th %ile |
| o3 | 2025 | SOTA en razonamiento | ARC-AGI: 87.5%, Frontier Math |
Aunque OpenAI no ha publicado paper, el consenso de la comunidad es:
DeepSeek-R1: razonamiento open-source
DeepSeek-R1 (DeepSeek AI, enero 2025) fue el primer modelo razonador open-source con rendimiento comparable a o1. Su paper reveló los detalles del entrenamiento que OpenAI no publicó.
📄 Paper
"DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning" arXiv:2501.12948 · GitHub
Proceso de entrenamiento de R1
El hallazgo de R1-Zero tiene implicaciones profundas para la IA: sugiere que el razonamiento puede emerger de la optimización por refuerzo sin necesidad de datos anotados explícitos de razonamiento. El modelo no fue entrenado con cadenas de pensamiento humanas — las descubrió por sí mismo porque resultaban útiles para maximizar la recompensa de respuestas correctas. Esto recuerda a cómo AlphaGo descubrió estrategias de Go que ningún humano había concebido. La distilación posterior (paso 5) permite transferir esta capacidad a modelos mucho más pequeños, lo que hace el razonamiento avanzado accesible incluso en hardware modesto.
Panorama de modelos razonadores
| Modelo | Org. | Open? | Técnica | Benchmark |
|---|---|---|---|---|
| o1 / o3 | OpenAI | ❌ | Internal CoT + RL | AIME 96.4% |
| DeepSeek-R1 | DeepSeek | ✅ MIT | GRPO, cold start, distill | AIME 79.8% |
| QwQ-32B | Alibaba | ✅ | RL, thinking tokens | AIME 79.5% |
| Gemini 2.0 Flash Thinking | ❌ | Thinking tokens | GPQA 74% | |
| Claude 3.5 (extended thinking) | Anthropic | ❌ | Extended CoT interna | — |
| Kimi k1.5 | Moonshot | ❌ | RL + long CoT | AIME 77.5% |
| Open-Reasoner-Zero | NVIDIA | ✅ | GRPO puro (inspirado en R1-Zero) | MATH 86.4% |
El campo de los modelos razonadores está evolucionando a una velocidad vertiginosa. Mientras que en 2024 solo existía o1-preview, a principios de 2025 ya había más de una docena de alternativas, varias de ellas open-source. La tendencia apunta a una convergencia: todos los LLMs de nueva generación incorporarán capacidades de razonamiento como característica estándar, no como un producto separado. La combinación de razonamiento con herramientas (ver sección de agentes) es especialmente prometedora: un agente que «piensa antes de actuar» comete menos errores y puede resolver tareas de mayor complejidad.
- "Scaling LLM Test-Time Compute" — Snell et al. arXiv:2408.03314
- "Let's Verify Step by Step" (PRM) — Lightman et al. arXiv:2305.20050
- "Large Language Monkeys" (escalado de test-time) — Brown et al. arXiv:2407.21787
- "s1: Simple Test-Time Scaling" — Muennighoff et al. arXiv:2501.19393
LLMs multimodales: más allá del texto
Los LLMs multimodales (MLLMs) extienden la capacidad de los LLMs para procesar y/o generar múltiples modalidades: texto, imágenes, audio, vídeo. No son modelos separados para cada modalidad, sino un único LLM que integra encoders especializados.
La motivación es clara: el mundo real es inherentemente multimodal. Los humanos combinamos visión, oído y lenguaje de forma fluida para entender nuestro entorno. Un LLM que solo procesa texto está fundamentalmente limitado: no puede interpretar un gráfico, diagnosticar una radiografía o entender una presentación. Los MLLMs rompen esta barrera, abriendo aplicaciones en medicina, educación, robótica, accesibilidad y creatividad que eran imposibles con modelos únicamente textuales. Para más contexto sobre los modelos de visión subyacentes, consulta el submódulo de Transformers, donde se cubren Vision Transformers (ViT).
Arquitectura: cómo se integran las modalidades
El patrón dominante para LLMs multimodales sigue una estructura de tres componentes:
| Componente | Función | Ejemplos |
|---|---|---|
| Vision Encoder | Convierte imagen en secuencia de embeddings | CLIP ViT-L, SigLIP, InternViT |
| Audio Encoder | Convierte audio en embeddings | Whisper encoder, USM |
| Projection Layer | Alinea embeddings visuales al espacio del LLM | MLP lineal, Q-Former, Perceiver Resampler |
| LLM Backbone | Procesa tokens multimodales autoregressivamente | LLaMA, Qwen, Gemma, Vicuna |
Modelos multimodales clave
CLIP (Contrastive Language-Image Pre-training) aprende un espacio compartido entre imágenes y texto mediante contrastive learning sobre 400M de pares (imagen, caption) de Internet.
CLIP no genera texto, pero sus embeddings son la base de muchos modelos multimodales.
LLaVA (Liu et al., 2023) conecta CLIP ViT con LLaMA mediante una simple proyección MLP. Es el modelo multimodal open-source más influyente:
- Pre-training: 595K pares imagen-caption para alinear vision y LLM
- Instruction tuning: 158K instrucciones visuales generadas por GPT-4
- LLaVA-1.5: mejoras con MLP proyector + datos mejores → SOTA en 11 benchmarks
GPT-4V (2023) fue el primer LLM comercial con capacidad de visión de alta calidad. GPT-4o (2024) va más allá: es natively multimodal, procesando texto, imagen y audio en un único modelo end-to-end, sin pipeline de ASR.
GPT-4o puede generar voz expresiva con emociones, cantar, y entender diagramas complejos.
Gemini (Google, 2023) fue entrenado desde el inicio como modelo multimodal (a diferencia de GPT-4V que añadió visión post-hoc). Gemini 1.5 Pro acepta hasta 1M tokens (1h de vídeo, 11h de audio, 30K líneas de código).
arXiv:2312.11805 (Gemini 1.0) · arXiv:2403.05530 (Gemini 1.5)
| Modelo | Modalidades | Vision Enc. | LLM | Open? | Innovación |
|---|---|---|---|---|---|
| LLaVA-1.5 | Imagen+Texto | CLIP ViT-L | Vicuna 13B | ✅ | Simple MLP projection |
| InternVL 2.5 | Imagen+Texto | InternViT-6B | InternLM2 | ✅ | Vision encoder enorme |
| Qwen-VL | Img+Txt+Bbox | ViT | Qwen 7B | ✅ | Grounding, multi-imagen |
| GPT-4o | Txt+Img+Audio | Nativo | GPT-4 | ❌ | Any-to-any nativo |
| Gemini 1.5 | Txt+Img+Audio+Video | Nativo | MoE | ❌ | 1M tokens context |
| Claude 3.5 | Imagen+Texto | — | Claude | ❌ | Excelente visión |
| Pixtral | Imagen+Texto | Custom ViT | Mistral | ✅ | Resolución variable |
Cómo se entrenan los LLMs multimodales
El entrenamiento típico sigue un proceso en fases:
Un aspecto sutil pero importante del entrenamiento multimodal es la resolución de imagen. Los primeros modelos (LLaVA-1.0) usaban resoluciones fijas de 224×224 píxeles, lo que limitaba la comprensión de detalles. Modelos más recientes (LLaVA-1.6, InternVL 2.5, Qwen-VL) soportan resoluciones variables y múltiples imágenes, dividiendo imágenes grandes en parches y procesando cada uno como una secuencia de tokens. Esto es análogo a cómo funcionan los modelos de difusión (ver submódulo de Modelos de Difusión) pero en el espacio de embeddings del LLM.
# ═══════════════════════════════════════════════════
# Inferencia multimodal con LLaVA
# ═══════════════════════════════════════════════════
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
from PIL import Image
import torch
# 1. Cargar modelo
processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
"llava-hf/llava-v1.6-mistral-7b-hf",
torch_dtype=torch.float16,
device_map="auto",
)
# 2. Preparar input multimodal
image = Image.open("mi_imagen.jpg")
prompt = "[INST] <image>\nDescribe esta imagen en detalle. ¿Qué objetos ves y qué relaciones hay entre ellos? [/INST]"
inputs = processor(prompt, image, return_tensors="pt").to("cuda")
# 3. Generar
output = model.generate(**inputs, max_new_tokens=300, do_sample=True, temperature=0.7)
print(processor.decode(output[0], skip_special_tokens=True))
📚 Papers y recursos multimodales
- CLIP: Radford et al. (2021) arXiv:2103.00020
- LLaVA: Liu et al. (2023) arXiv:2304.08485
- Flamingo: Alayrac et al. (2022) arXiv:2204.14198
- Whisper: Radford et al. (2022) arXiv:2212.04356
- ImageBind: Girdhar et al. (2023) arXiv:2305.05665
De LLMs a agentes: el salto
Un agente es un LLM que, además de generar texto, puede tomar acciones en el mundo real: ejecutar código, buscar en internet, enviar emails, interactuar con APIs, navegar por webs, etc. El LLM actúa como "cerebro" que decide qué herramienta usar y cuándo.
Los agentes representan un cambio de paradigma fundamental: pasamos de LLMs como generadores de texto a LLMs como sistemas autónomos de toma de decisiones. Mientras que un chatbot clásico solo puede hablar sobre el mundo, un agente puede actuar en él. Esto convierte al LLM en la pieza central de aplicaciones que antes requerían programación explícita: un agente puede investigar un tema, escribir código para analizar datos, crear visualizaciones y redactar un informe, todo de forma autónoma. El concepto conecta directamente con el aprendizaje por refuerzo, donde un agente interactúa con un entorno para maximizar una recompensa.
Function Calling: la base del agente
Function calling (o tool use) es la capacidad de un LLM de generar llamadas estructuradas a funciones externas en lugar de texto plano. El LLM no ejecuta la función — genera los parámetros en JSON y el sistema host la ejecuta.
# ═══════════════════════════════════════════════════
# Function Calling con OpenAI API
# ═══════════════════════════════════════════════════
from openai import OpenAI
import json
client = OpenAI()
# 1. Definir herramientas (schema JSON)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Obtener el clima actual de una ciudad",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Nombre de la ciudad"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "search_web",
"description": "Buscar información en internet",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Consulta de búsqueda"}
},
"required": ["query"]
}
}
}
]
# 2. Llamar al LLM
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "¿Qué tiempo hace en Madrid?"}],
tools=tools,
tool_choice="auto", # El modelo decide si usar herramienta
)
# 3. El modelo genera una llamada a función (no ejecuta)
tool_call = response.choices[0].message.tool_calls[0]
print(f"Función: {tool_call.function.name}")
print(f"Args: {tool_call.function.arguments}")
# → Función: get_weather
# → Args: {"city": "Madrid", "unit": "celsius"}
# 4. TÚ ejecutas la función y devuelves el resultado
result = get_weather(city="Madrid", unit="celsius") # Tu implementación
messages.append({"role": "tool", "content": json.dumps(result),
"tool_call_id": tool_call.id})
# 5. El LLM genera la respuesta final con el resultado
final = client.chat.completions.create(
model="gpt-4o", messages=messages
)
print(final.choices[0].message.content)
# → "En Madrid hace 22°C con cielo despejado."
MCP: Model Context Protocol
🔌 MCP (Anthropic, 2024)
El Model Context Protocol es un estándar abierto para conectar LLMs con herramientas y fuentes de datos de forma estandarizada. Define un protocolo cliente-servidor donde:
- MCP Server: expone herramientas, recursos y prompts
- MCP Client: el LLM/aplicación que consume las herramientas
- Transporte: stdio (local) o SSE/HTTP (remoto)
Frameworks de agentes
| Framework | Organización | Enfoque | Mejor para |
|---|---|---|---|
| LangChain | LangChain Inc. | Modular, chains/agents/tools | Prototipos, RAG, flujos complejos |
| LangGraph | LangChain Inc. | Grafos de estado, ciclos | Agentes multi-step con estado |
| CrewAI | CrewAI | Multi-agente, roles | Equipos de agentes colaborativos |
| AutoGen | Microsoft | Multi-agente conversacional | Debates entre agentes, código |
| Smolagents | Hugging Face | Code agents, minimalista | Agentes que escriben código Python |
| OpenAI Assistants | OpenAI | API managed, threads | Producción con OpenAI |
| Claude Code | Anthropic | Agente de código CLI | Desarrollo de software autónomo |
# ═══════════════════════════════════════════════════
# Agente ReAct con LangGraph
# ═══════════════════════════════════════════════════
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
# 1. Definir herramientas
@tool
def calculate(expression: str) -> str:
"""Evalúa una expresión matemática."""
return str(eval(expression))
@tool
def search(query: str) -> str:
"""Busca información en una base de conocimiento."""
# Tu implementación de búsqueda
return f"Resultado para: {query}"
# 2. Crear agente ReAct
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = create_react_agent(llm, tools=[calculate, search])
# 3. Ejecutar
result = agent.invoke({
"messages": [("user", "¿Cuánto es el PIB de España dividido por su población?")]
})
# El agente internamente:
# Thought: Necesito buscar el PIB y la población de España
# Action: search("PIB España 2024")
# Observation: "El PIB de España en 2024 es ~1.5T EUR"
# Action: search("población España 2024")
# Observation: "La población de España es ~48M"
# Action: calculate("1500000000000 / 48000000")
# Observation: "31250.0"
# Answer: "El PIB per cápita de España es ~31,250 EUR"
Patrones de diseño de agentes
El patrón más popular. El agente alterna entre razonamiento (Thought), acción (Action) y observación (Observation) hasta llegar a la respuesta.
Un planificador (LLM) crea un plan de alto nivel (lista de pasos), y un ejecutor (otro LLM o el mismo) ejecuta cada paso. Permite replanning si un paso falla.
Múltiples agentes con roles diferentes (researcher, coder, reviewer) colaboran para resolver una tarea compleja. Pueden debatir, revisar el trabajo de otros, y llegar a consenso. Ejemplos: AutoGen, CrewAI, ChatDev.
arXiv:2308.00352 (MetaGPT)
Reflexion (Shinn et al., 2023): el agente revisa su propia salida, identifica errores, y se corrige. Usa un "critic" o autoevaluación para mejorar iterativamente.
En lugar de usar herramientas predefinidas, el agente escribe y ejecuta código Python como su mecanismo de acción principal. Más flexible y composable que function calling.
Usado por: HuggingFace smolagents, OpenAI Code Interpreter, Claude Artifacts. Ref: arXiv:2401.00812 (Executable Code Actions)
Evaluación, limitaciones y futuro
| Benchmark | Qué evalúa | SOTA |
|---|---|---|
| SWE-bench | Resolver issues reales de GitHub | ~50% (Claude 3.5 + agentic) |
| WebArena | Tareas web (navegar, comprar, buscar) | ~35% |
| GAIA | Tareas generales con herramientas | ~70% (nivel 1) |
| TAU-bench | Tool use real (airline, retail) | ~50% |
| HumanEval | Generación de código | ~95%+ (GPT-4o, Claude 3.5) |
- Reliability: los agentes aún fallan en ~30-70% de tareas complejas
- Error compounding: cada paso tiene probabilidad de error, que se acumula
- Cost: muchas llamadas a la API, tokens de reasoning → caro
- Safety: un agente con acceso a herramientas puede causar daño real
- Evaluation: difícil medir el progreso sin benchmarks estandarizados
A pesar de estas limitaciones, los agentes están rápidamente entrando en producción. GitHub Copilot, Cursor y Devin demuestran que los agentes de código ya son útiles en tareas reales de desarrollo de software. En el ámbito empresarial, agentes de atención al cliente, análisis de datos y automatización de procesos están siendo desplegados con éxito — siempre con human-in-the-loop para acciones críticas. La clave está en diseñar sistemas que combinen la flexibilidad de los agentes con la supervisión humana adecuada: dar al agente autonomia en tareas de bajo riesgo y requerir aprobación explícita para acciones irreversibles.
📚 Papers y recursos de agentes
- ReAct: Yao et al. (2022) arXiv:2210.03629
- Toolformer: Schick et al. (2023) arXiv:2302.04761
- Voyager: Wang et al. (2023) arXiv:2305.16291
- Survey: LLM-based Agents: Wang et al. (2024) arXiv:2308.11432
- LangChain: GitHub
- LangGraph: GitHub
🏗️ Selector de arquitectura de agente
Según tu caso de uso, te recomendamos el patrón y framework de agente más adecuado.
Recursos y referencias generales
| Recurso | Tipo | Enlace |
|---|---|---|
| Andrej Karpathy — "Let's build GPT" | Video/Tutorial | YouTube |
| Lilian Weng — "LLM Powered Autonomous Agents" | Blog | Blog |
| Jay Alammar — "The Illustrated Transformer" | Blog visual | Blog |
| Sebastian Raschka — "Build an LLM from Scratch" | Libro | GitHub |
| Hugging Face NLP Course | Curso | HF Course |
| LMSYS Chatbot Arena | Benchmark | Arena |
| Open LLM Leaderboard | Rankings | HF Spaces |
| Awesome LLM — curated list | Lista | GitHub |