Phase 07 - Lesson 14

Construye un Transformer desde Cero — El Proyecto Final

Trece lecciones. Un modelo. Sin atajos.

Tipo: Construcción Lenguajes: Python Prerrequisitos: Fase 7 · 01 a 13. No te lo saltes. Tiempo: ~120 minutos

El Problema

Has leído todos los artículos. Has implementado atención, divisiones multi-cabezal, codificaciones posicionales, bloques de codificador y decodificador, pérdidas de BERT y GPT, MoE, KV cache. Ahora haz que funcionen juntos en una tarea real.

El proyecto final: entrenar un pequeño transformer solo-decodificador (decoder-only) de extremo a extremo en una tarea de modelado de lenguaje a nivel de caracteres. Lee a Shakespeare. Genera nuevo texto al estilo de Shakespeare. Es lo suficientemente pequeño como para entrenarlo en una computadora portátil en menos de 10 minutos. Es lo suficientemente correcto como para que, si cambias por un conjunto de datos más grande y un entrenamiento más largo, obtengas un LM real.

Este es el "nanoGPT" del curso. No es original — el tutorial nanoGPT de 2023 de Karpathy es la implementación de referencia que cada estudiante escribe al menos una vez. Tomamos la estructura y la adaptamos en torno a lo que hemos cubierto.

El Concepto

Diagrama de bloques del Transformer desde cero

La arquitectura, anotada:

input tokens (B, N)
   │
   ▼
token embedding + positional embedding  ◀── Lesson 04 (RoPE option)
   │
   ▼
┌──── block × L ────────────────────┐
│  RMSNorm                          │  ◀── Lesson 05
│  MultiHeadAttention (causal)      │  ◀── Lesson 03 + 07 (causal mask)
│  residual                         │
│  RMSNorm                          │
│  SwiGLU FFN                       │  ◀── Lesson 05
│  residual                         │
└────────────────────────────────── ┘
   │
   ▼
final RMSNorm
   │
   ▼
lm_head (tied to token embedding)
   │
   ▼
logits (B, N, V)
   │
   ▼
shift-by-one cross-entropy            ◀── Lesson 07

Qué entregamos

  • GPTConfig — un solo lugar para configurar todos los hiperparámetros.
  • MultiHeadAttention — causal, por lotes (batched), con una ruta opcional al estilo Flash (scaled_dot_product_attention de PyTorch).
  • SwiGLUFFN — FFN moderno.
  • Block — pre-normalización (pre-norm), atención envuelta en residuos (residual-wrapped) + FFN.
  • GPT — embeddings, bloques apilados, cabezal LM (LM head), generate().
  • Bucle de entrenamiento con AdamW, tasa de aprendizaje con decaimiento de coseno (cosine LR), recorte de gradiente (gradient clipping).
  • Tokenizador a nivel de caracteres sobre texto de Shakespeare.

Qué no entregamos

  • RoPE — implementado conceptualmente en la Lección 04. Aquí usamos embeddings posicionales aprendidos por simplicidad. Los ejercicios te piden que lo cambies por RoPE.
  • KV cache durante la generación — cada paso de generación recalcula la atención sobre todo el prefijo. Más lento pero más simple. Los ejercicios te piden que agregues un KV cache.
  • Flash Attention — PyTorch 2.0+ lo despacha automáticamente si las entradas coinciden; usamos F.scaled_dot_product_attention.
  • MoE — un solo FFN por bloque. Viste MoE en la Lección 11.

Métricas objetivo

En una computadora portátil Mac M2, un GPT de 4 capas, 4 cabezales y d_model=128 entrenado durante 2,000 pasos en tinyshakespeare.txt:

  • La pérdida de entrenamiento converge de ~4.2 (aleatoria) a ~1.5 en unos 6 minutos.
  • La salida muestreada tiene el estilo de Shakespeare: surgen palabras arcaicas, saltos de línea y nombres propios como "ROMEO:".
  • La pérdida de validación (el 10% final del texto reservado) sigue de cerca la pérdida de entrenamiento; no hay sobreajuste (overfitting) con este tamaño/presupuesto.

Constrúyelo

Esta lección usa PyTorch. Instala torch (la versión para CPU está bien). Consulta code/main.py. El script se encarga de:

  • Descargar tinyshakespeare.txt si no está presente (o leer una copia local).
  • Tokenizador de caracteres a nivel de bytes.
  • División de entrenamiento/validación en 90/10.
  • Bucle de entrenamiento con bf16 autocast en hardware compatible.
  • Muestreo después de completar el entrenamiento.

Paso 1: datos

text = open("tinyshakespeare.txt").read()
chars = sorted(set(text))
stoi = {c: i for i, c in enumerate(chars)}
itos = {i: c for c, i in stoi.items()}
encode = lambda s: [stoi[c] for c in s]
decode = lambda xs: "".join(itos[x] for x in xs)

65 caracteres únicos. Vocabulario diminuto. Cabe en un vocab_size de 4 bytes. Sin BPE, sin dramas con el tokenizador.

Paso 2: modelo

Consulta code/main.py. El bloque es de manual de la Lección 05 — pre-norm, RMSNorm, SwiGLU, MHA causal. Cantidad de parámetros para 4/4/128: ~800K.

Paso 3: bucle de entrenamiento

Obtén un lote (batch) aleatorio de ventanas de tokens de longitud 256. Forward. Entropía cruzada desplazada por uno (shift-by-one cross-entropy). Backward. Paso de AdamW. Log. Repita.

for step in range(max_steps):
    x, y = get_batch("train")
    logits = model(x)
    loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1))
    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
    opt.step()
    opt.zero_grad()

Paso 4: muestreo

Dado un prompt, realiza forward repetidamente, toma muestras de los logits top-p, añade y continúa. Detente después de 500 tokens.

Paso 5: lee la salida

Después de 2,000 pasos:

ROMEO:
Away and mild will not thy friend, that thou shalt wit:
The chief that well shame and hath been his friends,
...

No es Shakespeare. Pero tiene estilo de Shakespeare. Una clara victoria para ~800K parámetros y 6 minutos en una computadora portátil.

Úsalo

Este proyecto final es una arquitectura de referencia. Tres extensiones para convertirlo en algo real:

  1. Cambiar el tokenizador. Usa BPE (por ejemplo, tiktoken.get_encoding("cl100k_base")). El tamaño del vocabulario salta de 65 a ~50,000. La capacidad del modelo debe escalarse para compensar.
  2. Entrenar en un corpus más grande. Usa OpenWebText o fineweb-edu (HuggingFace). 10B de tokens en una sola A100 toman ~24 horas para un GPT de 125M de parámetros.
  3. Agregar RoPE + KV cache + Flash Attention. Los siguientes ejercicios te guiarán por cada uno.

Esto termina siendo un GPT de 125 millones de parámetros que genera inglés fluido. No es un modelo de frontera. Pero la misma ruta de código — solo que más grande — es lo que Karpathy, EleutherAI y el Allen Institute usan para entrenar checkpoints de investigación en 2026.

Entrégalo

Consulta outputs/skill-transformer-review.md. La habilidad (skill) revisa la corrección de una implementación de transformer desde cero a lo largo de las 13 lecciones anteriores.

Ejercicios

  1. Fácil. Ejecuta code/main.py. Verifica que la pérdida de validación en el último paso de tu modelo entrenado sea menor a 2.0. Cambia max_steps de 2,000 a 5,000; ¿sigue mejorando la pérdida de validación?
  2. Medio. Reemplaza los embeddings posicionales aprendidos con RoPE. Aplica la rotación a Q y K dentro de MultiHeadAttention. Entrena y verifica que la pérdida de validación sea al menos tan baja.
  3. Medio. Implementa un KV cache en el bucle de muestreo. Genera 500 tokens con y sin cache. El tiempo de ejecución real (wall-clock) debería mejorar de 5 a 20 veces en una computadora portátil.
  4. Difícil. Agrega un segundo cabezal al modelo que prediga el token siguiente al próximo (MTP — Multi-Token Prediction de DeepSeek-V3). Entrenen conjuntamente. ¿Ayuda?
  5. Difícil. Reemplaza el FFN único por bloque con una MoE de 4 expertos. Enrutador + enrutamiento top-2. Observa cómo cambia la pérdida de validación al igualar los parámetros activos.

Términos Clave

Término Lo que la gente dice Lo que realmente significa
nanoGPT "El repositorio del tutorial de Karpathy" Código de entrenamiento mínimo para un transformer solo-decodificador, ~300 LOC (líneas de código); la referencia canónica.
tinyshakespeare "El corpus de juguete estándar" ~1.1 MB de texto; todos los tutoriales de LM a nivel de caracteres desde 2015 lo usan.
Tied embeddings "Compartir la matriz de entrada/salida" Peso del cabezal LM = transpuesta de la matriz de embedding de tokens; ahorra parámetros, mejora la calidad.
bf16 autocast "Truco de precisión de entrenamiento" Ejecuta forward/back en bf16, mantiene el estado del optimizador en fp32; estándar desde 2021.
Gradient clipping "Evita picos" Limita la norma global del gradiente a 1.0; evita que el entrenamiento explote.
Cosine LR schedule "El valor predeterminado a partir de 2020" La LR aumenta linealmente (warmup) y luego decae en forma de coseno hasta el 10% del pico.
MFU "Utilización de FLOPs del modelo" FLOPs logrados / pico teórico; 40% denso, 30% MoE es un resultado fuerte en 2026.
Val loss "Pérdida en datos no vistos" Entropía cruzada en datos que el modelo nunca vio; detector de sobreajuste.

Leituras Adicionais -> No, in Spanish:

Lecturas Adicionales

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).