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étrica | Qué mide | Cómo se lee |
|---|---|---|
train_loss | Pérdida sobre el lote de entrenamiento actual. | Debe bajar; ruidosa por naturaleza. |
val_loss | Pérdida sobre datos no vistos durante el ajuste. | Es la que importa para generalizar. |
grad_norm | Magnitud del gradiente $\lVert g \rVert$ antes del clipping. | Picos = inestabilidad; ceros = nada aprende. |
lr | Learning rate efectivo en ese paso (lo mueve el scheduler). | Verifica warmup y decaimiento reales. |
tokens/s | Rendimiento: tokens procesados por segundo. | Caídas = cuello de botella de I/O o CPU. |
VRAM | Memoria 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
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_lossdeja de mejorar, aunquetrain_losssiga bajando.
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:
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):
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:
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):
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:
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.
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.
Ambas altas y planas: nada aprende. lr demasiado bajo, muy pocos pasos o capacidad insuficiente. Solución: subir $\eta$, más epochs, revisar datos.
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:
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:
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.
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.
- Instrumenta el entrenamiento: loguea
train_loss,val_loss,grad_normylrpor step, no solo al final. - Corre un sweep de learning rate sobre
{1e-5, 5e-5, 2e-4}dejando lo demás fijo. - Prueba dos schedules:
{constante, cosine+warmup}, y observa cómo cambia la forma de la curva delry de la loss. - Superpón las curvas de todos los runs en un mismo dashboard (TensorBoard o Weights & Biases).
- Identifica los tres casos: el run que diverge, el que hace underfitting y el punto de early-stop (mínimo de
val_loss).
Trainer · tensorboard / wandbAquí 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.
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}$.
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$.
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.
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.
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.
- 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.