Módulo 04 · Entrenamiento

Hiperparámetros y curvas de loss

No existe una tabla de valores universales que sirva para todo modelo, todo dataset y toda tarea. Lo que sí se transfiere es la capacidad de leer lo que el entrenamiento te está diciendo. Un run emite un flujo de métricas — train_loss, val_loss, grad_norm, lr, tokens/s, VRAM — y cada hiperparámetro es una perilla cuyo efecto aparece en esas curvas. Este módulo enseña a interpretar las métricas y a reconocer los tres patrones de fallo antes de desperdiciar horas de GPU.

Al terminar sabrás

  • Leer un dashboard de entrenamiento y explicar qué mide cada serie: train_loss, val_loss, grad_norm, lr, tokens/s, VRAM.
  • Distinguir qué hiperparámetro mueve qué curva: learning_rate, batch, gradient_accumulation, warmup, weight_decay, scheduler.
  • Reconocer de un vistazo los tres patrones: overfitting, underfitting y loss engañosa.
  • Calcular el batch efectivo y el número de pasos, y saber cuándo gradient_accumulation sustituye a más VRAM.
  • Elegir el punto de early stopping desde la brecha de generalización, no adivinando.

1Un run es un flujo de métricas

Entrenar no es apretar "start" y esperar. Cada cierto número de pasos el bucle emite un conjunto de números. Antes de tocar ningún hiperparámetro conviene saber leer qué es cada uno, porque son la única evidencia de lo que está pasando dentro:

MétricaQué mideCómo se lee
train_lossPérdida sobre el lote de entrenamiento actual.Debe bajar; ruidosa por naturaleza.
val_lossPérdida sobre datos no vistos durante el ajuste.Es la que importa para generalizar.
grad_normMagnitud del gradiente $\lVert g \rVert$ antes del clipping.Picos = inestabilidad; ceros = nada aprende.
lrLearning rate efectivo en ese paso (lo mueve el scheduler).Verifica warmup y decaimiento reales.
tokens/sRendimiento: tokens procesados por segundo.Caídas = cuello de botella de I/O o CPU.
VRAMMemoria de GPU ocupada.Cerca del tope = riesgo de OOM.
step 120  loss=1.842  val_loss=1.977  grad_norm=0.91  lr=1.8e-4  tok/s=5310  vram=14.2G
step 240  loss=1.401  val_loss=1.688  grad_norm=1.14  lr=1.6e-4  tok/s=5288  vram=14.2G
Una línea de log por evaluación: seis series que hay que leer juntas, nunca aisladas.

2Los hiperparámetros y qué curva mueve cada uno

Un hiperparámetro es una decisión que fijas antes de entrenar y que no se aprende. Lo útil no es memorizar valores, sino asociar cada perilla con su síntoma observable:

  • learning_rate ($\eta$): el tamaño del paso. Demasiado alto → la loss diverge o rebota; demasiado bajo → baja lentísimo (underfitting por falta de pasos). Es el hiperparámetro más sensible.
  • epochs: cuántas pasadas completas sobre el dataset. Más epochs sin control = camino directo al overfitting.
  • batch (micro-batch): ejemplos por paso hacia adelante. Más grande = gradiente menos ruidoso, más VRAM.
  • gradient_accumulation: acumula gradientes de varios micro-lotes antes de actualizar. Simula un batch grande sin gastar más VRAM, a cambio de más tiempo por actualización.
  • warmup: arranca con $\eta$ pequeño y súbelo linealmente. Evita que los primeros pasos, con gradientes enormes, rompan los pesos.
  • weight_decay ($\lambda$): regularización que empuja los pesos hacia cero. Combate el overfitting penalizando pesos grandes.
  • max_length: longitud de secuencia. Más largo = más contexto pero coste cuadrático en atención y más VRAM.
  • scheduler: la forma en que $\eta$ evoluciona (constante, lineal, cosine…). Define cómo "aterriza" el entrenamiento.
  • grad clipping: recorta $\lVert g \rVert$ a un máximo. Impide que un lote tóxico dispare la actualización.
  • early stopping: detener cuando val_loss deja de mejorar, aunque train_loss siga bajando.
Nota · el batch que de verdad importa

Cuando alguien reporta "batch 256" rara vez cabe en una GPU. El número real es el batch efectivo: micro-batch × acumulación × número de dispositivos. Reproducir un resultado exige igualar el batch efectivo, no el micro-batch que casualmente entró en tu tarjeta.

3AdamW: el optimizador por defecto

El optimizador decide cómo convertir un gradiente en una actualización de pesos. AdamW mantiene dos medias móviles por parámetro — del gradiente ($m_t$) y de su cuadrado ($v_t$) — que estiman el momento y la escala:

$$ m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t, \qquad v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 $$

Como $m_t$ y $v_t$ arrancan en cero, están sesgados hacia abajo al principio; se corrige ese sesgo antes de usarlos, y la actualización aplica el weight decay desacoplado $\lambda$ (de ahí la "W" de AdamW: la regularización va aparte del gradiente adaptativo):

$$ \hat m_t = \frac{m_t}{1-\beta_1^{\,t}}, \qquad \hat v_t = \frac{v_t}{1-\beta_2^{\,t}} $$
$$ \theta_t = \theta_{t-1} - \eta\!\left(\dfrac{\hat m_t}{\sqrt{\hat v_t}+\epsilon} + \lambda\,\theta_{t-1}\right) $$

Leer esto en el dashboard: si grad_norm explota, ninguna media móvil salva la actualización — por eso el clipping actúa sobre $g_t$ antes de que entre aquí.

4El scheduler: warmup y cosine

El learning rate rara vez es constante. Durante el warmup sube linealmente desde cero hasta $\eta_{\max}$, para no romper los pesos con los gradientes grandes del arranque:

$$ \eta(s) = \eta_{\max}\cdot \dfrac{s}{s_{\text{warm}}} \qquad \text{para } s \le s_{\text{warm}} $$

Pasado el warmup, el schedule cosine baja suavemente hasta un mínimo, dando pasos grandes al principio (cuando hay mucho que aprender) y pequeños al final (para asentar):

$$ \eta(s) = \eta_{\min} + \tfrac12(\eta_{\max}-\eta_{\min})\left(1 + \cos\!\left(\pi\,\dfrac{s - s_{\text{warm}}}{s_{\text{total}} - s_{\text{warm}}}\right)\right) $$

En la serie lr del dashboard esto se ve como una rampa seguida de una media campana descendente. Si tu lr no dibuja lo que esperas, el bug está en la configuración del scheduler, no en el modelo.

5Batch efectivo y número de pasos

El tamaño de batch que de verdad ve el optimizador combina tres factores. De él se deriva cuántos pasos de actualización habrá en todo el entrenamiento:

$$ B_{\text{eff}} = B_{\text{micro}}\cdot \text{grad\_accum}\cdot n_{\text{dev}}, \qquad \text{pasos} = \left\lceil \dfrac{N}{B_{\text{eff}}} \right\rceil \cdot \text{epochs} $$

Con $N$ el número de ejemplos. Esto explica por qué subir gradient_accumulation reduce el número de pasos (cada actualización consume más datos) y por qué, a batch efectivo fijo, suele convenir reajustar $\eta$. Es también la cuenta que hace falta para dimensionar el warmup: un s_warm en pasos que no conoce el total del run es un warmup mal puesto.

6Los tres patrones de curvas

Casi todo lo que sale mal en un fine-tuning cae en uno de tres patrones. Aprender a nombrarlos a partir de la forma de las curvas es la mitad del trabajo de diagnóstico.

Overfitting

train sigue bajando, val tocó fondo y vuelve a subir. El modelo memoriza. Solución: menos epochs, más weight_decay, early stop en el mínimo de val.

Underfitting

Ambas altas y planas: nada aprende. lr demasiado bajo, muy pocos pasos o capacidad insuficiente. Solución: subir $\eta$, más epochs, revisar datos.

Loss engañosa

La loss baja pero las respuestas empeoran: formato memorizado, catástrofe de olvido o fuga de datos. La métrica miente; hay que leer generaciones.

Formalmente, el overfitting es una brecha de generalización creciente entre val y train, y el criterio de parada es el paso donde val toca su mínimo:

$$ \text{brecha}(s) = \mathcal{L}_{\text{val}}(s) - \mathcal{L}_{\text{train}}(s), \qquad s^{\star} = \arg\min_s \mathcal{L}_{\text{val}}(s) $$

El tercer patrón es el más peligroso porque ninguna curva lo delata: una loss que baja puede convivir con respuestas peores. Por eso conviene traducir la loss a una escala más intuitiva — la perplexity — y, sobre todo, mirar generaciones reales:

$$ \mathrm{PPL} = e^{\mathcal{L}} $$
Cuidado

Una val_loss baja no garantiza un modelo mejor. Puede haber aprendido a imitar el formato del dataset mientras olvida capacidades previas (olvido catastrófico). La loss es una guía, no un veredicto: siempre cierra el ciclo leyendo salidas del modelo sobre prompts reales.

En el teléfono

Los hiperparámetros de este módulo son de tiempo de entrenamiento: lr, epochs, warmup, scheduler — nada de esto existe on-device, donde solo corre la inferencia. Pero su efecto sí llega al teléfono por otra vía: un modelo sobre-entrenado (overfit) generaliza peor, y eso se nota como respuestas rígidas, memorizadas, que fallan fuera del formato del dataset. Elegir bien lr y epochs —y parar en el mínimo de val_loss— es parte de que el modelo final sirva cuando lo empaquetes en la app.

Cómo practicar
  1. Instrumenta el entrenamiento: loguea train_loss, val_loss, grad_norm y lr por step, no solo al final.
  2. Corre un sweep de learning rate sobre {1e-5, 5e-5, 2e-4} dejando lo demás fijo.
  3. Prueba dos schedules: {constante, cosine+warmup}, y observa cómo cambia la forma de la curva de lr y de la loss.
  4. Superpón las curvas de todos los runs en un mismo dashboard (TensorBoard o Weights & Biases).
  5. Identifica los tres casos: el run que diverge, el que hace underfitting y el punto de early-stop (mínimo de val_loss).
Herramientas: Python · transformers Trainer · tensorboard / wandb
Rust · on-device

Aquí Rust no participa. El bucle de entrenamiento, el ajuste de hiperparámetros y el monitoreo de curvas son territorio de Python — es donde vive el ecosistema. El turno de Rust llega en la inferencia, cuando el modelo ya entrenado corre en el dispositivo (Módulos 9-10).

Lecturas y recursos

Ejercicios

De menor a mayor complejidad. El último es el que hace un practicante de verdad.

Ejercicio 1 · calentamiento

Grafica un scheduler

Implementa las funciones de lr para warmup lineal y cosine, y grafica $\eta(s)$ sobre 1000 pasos con s_warm = 100. Verifica visualmente la rampa y la campana descendente.

Entrega: una figura con las dos curvas de lr vs. paso.   Pista: usa las fórmulas de warmup y cosine del módulo; comprueba $\eta(s_{\text{warm}}) = \eta_{\max}$.

Ejercicio 2

Calcula el batch efectivo

Dado un dataset de $N = 12000$ ejemplos, B_micro = 4, grad_accum = 8 y 2 GPUs, calcula $B_{\text{eff}}$ y el número total de pasos para 3 epochs. Repite si duplicas grad_accum y explica el efecto en el número de pasos.

Entrega: los dos cálculos con la fórmula.   Pista: $B_{\text{eff}} = B_{\text{micro}}\cdot \text{grad\_accum}\cdot n_{\text{dev}}$; recuerda el techo $\lceil\cdot\rceil$.

Ejercicio 3

Clasifica curvas reales

Toma tres pares de curvas train/val (dados o generados) y etiqueta cada uno como overfitting, underfitting o sano. Justifica con la forma y la brecha de generalización.

Entrega: tabla curva → diagnóstico → justificación.   Pista: calcula $\mathcal{L}_{\text{val}} - \mathcal{L}_{\text{train}}$ por paso y mira su tendencia.

Ejercicio 4

Loss vs. calidad real

Entrena (o simula) un run que baje val_loss y, en tres checkpoints, genera respuestas sobre 5 prompts fijos. Muestra un caso donde una loss menor no implica mejores generaciones y nómbralo como "loss engañosa".

Entrega: tabla checkpoint → val_loss → PPL → nota cualitativa de las generaciones.   Pista: $\mathrm{PPL} = e^{\mathcal{L}}$; compara formato vs. contenido.

Ejercicio 5 · ejercicio top

Sweep de LR × schedule en un dashboard

Corre un pequeño sweep de LR (p.ej. 1e-5, 5e-5, 2e-4) × schedule {constante, cosine+warmup} sobre Qwen3-0.6B, registrando train y val loss por step en un solo dashboard; identifica el run que diverge, el que hace underfitting, y el run cuyo mínimo de val-loss ocurre antes del último epoch (punto de early-stop); anota cada curva con su modo de fallo.

Entrega: una figura con las curvas train/val superpuestas y el $(\eta, \text{schedule}, \text{step de parada})$ elegido justificado desde las curvas, no adivinado.   Pista: loguea a un mismo panel (p.ej. TensorBoard/W&B); marca $s^{\star} = \arg\min_s \mathcal{L}_{\text{val}}(s)$ en cada run.

Puntos clave
  • No memorices valores: aprende a leer las seis series del dashboard juntas.
  • Cada hiperparámetro es una perilla con un síntoma observable en alguna curva.
  • El batch que importa es el efectivo: micro × acumulación × dispositivos.
  • Tres patrones: overfitting (val sube), underfitting (ambas planas), loss engañosa (baja pero peor).
  • Early stop en $\arg\min_s \mathcal{L}_{\text{val}}$; y siempre cierra leyendo generaciones reales.