English · Español
Fase 19 — Árbol de decisión de estabilidad (spikes de loss, NaN, precisión mixta)¶
La Fase 18 te enseñó a configurar el run. La Fase 19 te enseña a leer su dinámica cuando algo se rompe en caliente. Este árbol cubre spikes, NaN, overflow fp16 y divergencia. Antes de invocarlo, asegúrate de haber pasado el árbol de la Fase 18.
Un checklist ejecutable para cuando un run configurado en la Fase 18 empieza a portarse mal en vuelo. Prerrequisito: has recorrido docs/phase-18-training-loop/stability-check.md y todo ha pasado.
Cómo usarlo¶
- El run arrancó sano. Algo se torció después del paso \(K\).
- Ten abiertos
dashboard.htmlyexperiments/<run>/manifest.json. - Empieza por §1 (la comprobación de NaN es la más rápida). Si hay NaN, ve a §1 y para. Si no, §2 (spike). Si no, §3 (drift). Si no, §4 (divergencia silenciosa).
§1 — ¿Es el loss NaN o Inf?¶
Q1.1 ¿Es loss(step $t$) NaN o Inf en algún paso logueado?
- Si sí: tienes un update destructivo. Ve a §1.2.
- Si no: ve a §2.
Q1.2 ¿Cuál era el régimen de dtype en el run?
- fp32: NaN en fp32 es raro. Casi siempre log(0) en el loss (la probabilidad fue exactamente a 0 por un logit pre-softmax gigante) o 0/0 en la reducción del loss. Encuentra la op culpable con comprobaciones de np.isnan(...).any() en cada capa en un único forward pass tras recarga.
- fp16: la magnitud de activación excedió 65504. Ve a §3 (precisión mixta).
- bf16: menos común, pero el overflow de exponente en bf16 sigue ocurriendo en \(\sim 10^{38}\). Mismo camino que fp32, con la posibilidad adicional de que el gradiente haya hecho overflow (la precisión de gradiente bf16 son 7 bits — 1% de ruido por op).
Q1.3 ¿Dónde se originó el NaN?
- Recarga el último checkpoint libre de NaN.
- Avanza un batch con comprobaciones de np.isnan en: salida de embedding, salida de atención de cada bloque, salida del MLP de cada bloque, LN final, logits de la LM-head, softmax, log-softmax, reducción del loss.
- La primera capa que reporta NaN con input finito es la culpable.
Q1.4 Fix:
- log(0) en el loss: añadir \(\epsilon\) al denominador del softmax, o usar log_softmax en lugar de log(softmax(x)).
- Gradiente de embedding = \(\infty\): clip-by-value sobre los gradientes (además del clip-by-norm). Tope en \(\pm 100\).
- Overflow de activación en fp16: activar loss scaling dinámico (Fase 19 §3) o cambiar a bf16.
Si los fixes de §1.4 se aplican y el re-run sigue dando NaN, el bug es estructural — vuelve a la fase 18 stability-check §5 (arquitectura del modelo).
§2 — Spike de loss (recuperable o no)¶
Q2.1 ¿Hay un único paso donde el loss \(\geq 3\sigma\) por encima de la media móvil de 100 pasos?
- Calcula \(\mu = \text{mean}(\text{loss}[t-100:t])\), \(\sigma = \text{std}(\text{loss}[t-100:t])\) para \(t > 100\).
- Una spike es
loss[t] > μ + 3σ. - Si sí: ve a §2.2. Si no: ve a §3.
Q2.2 ¿Se recupera el loss a dentro de \(\mu + 0.5\sigma\) en 50 pasos? - Sí (spike recuperable): ve a §2.3. - No (elevación persistente): ve a §2.4.
Q2.3 Spike recuperable — encuentra la causa.
- Panel: grad-norm pre-clip en el paso de la spike. Si \(> 30 \mu_g\) donde \(\mu_g\) es la media móvil de grad-norm: token de cola larga en el batch. Abre theory/04-loss-spike-postmortem-template.md y síguelo.
- Panel: LR en el paso de la spike. Si es discontinuo (saltó): bug del schedule.
- Panel: composición del batch. Si el índice del batch es duplicado de un batch reciente: bug del data-loader.
- Aplica el fix (batching estratificado, fix del schedule o fix del loader). Re-ejecuta.
Q2.4 Elevación persistente — encuentra la causa. - Los momentos del optimizador están ahora corrompidos; el modelo está atascado en una región peor. - Recarga el último checkpoint pre-spike (haces checkpoint cada \(K=200\) pasos, ¿verdad?). - Aplica un fix preventivo (baja el umbral de clip a 0.5; activa batching estratificado) antes del reinicio. - Si no hay checkpoint, el coste es el re-entrenamiento completo.
§3 — Comprobaciones de precisión mixta (fp16/bf16)¶
Sólo relevante si dtype es fp16 o bf16 en manifest.json.
Q3.1 ¿Hay un paso con grad-norm = inf o NaN?
- Sí: overflow fp16. La magnitud de activación excedió \(65504\) (el máximo fp16).
- No: ve a §4.
Q3.2 ¿Qué muestra el histórico del loss-scale?
- El loss scaler dinámico debería estar doblando cada \(N\) pasos consecutivos sin overflow y partiéndose a la mitad en cada overflow.
- Si el loss scale es constante: el escalado dinámico no está activo. Actívalo.
- Si el loss scale ha colapsado a \(< 1\): overflow persistente. Las magnitudes de activación no son sólo transitorias — son sistemáticamente demasiado grandes. Revisa init (Fase 18 stability §5), revisa la magnitud del residual stream.
Q3.3 Firma del overflow fp16.
La huella diagnóstica:
| Señal | Overflow fp16 | NaN fp32 |
|---|---|---|
| El primer NaN aparece en | gradiente o activación | loss directamente |
loss scale antes del NaN |
finito, luego partiéndose | N/A |
| ¿Forward pass libre de NaN? | a veces | rara vez |
| Fix | activar / arreglar el loss scaling |
arquitectural |
Si dudas: re-ejecuta un único paso en fp32 con los mismos datos. Si funciona en fp32, tienes un overflow fp16, no un bug estructural.
Q3.4 Fixes:
- Activa torch.cuda.amp.GradScaler (nativo de PyTorch) o su equivalente.
- Reduce el LR un 2× si el loss-scale sigue colapsando.
- Cambia fp16 → bf16 (rango de exponente más ancho, menos precisión; suele ser más seguro para transformers).
§4 — Divergencia silenciosa¶
El loss no está haciendo spike, no es NaN, sólo está subiendo lentamente.
Q4.1 ¿Ha aumentado el loss de forma monótona en los últimos 200 pasos? - Sí: ve a §4.2. No: el run está sano (o sanamente ruidoso); deja de recorrer.
Q4.2 ¿Ha aumentado el LR en la misma ventana? - Sí: schedule mal configurado (coseno implementado como coseno inverso, o el warmup no termina nunca). - No: ve a §4.3.
Q4.3 ¿Ha cambiado la norma de pesos de algún grupo de parámetros más de 2× en los últimos 200 pasos?
- Sí: el weight decay es demasiado bajo (la norma sube) o demasiado alto (la norma colapsa). Ajusta un 2× y re-ejecuta desde el checkpoint.
- No: ve a §4.4.
Q4.4 ¿Está la media móvil de la norma de gradiente subiendo? - Sí: el paisaje de loss se está volviendo más difícil (te alejas de un mínimo). La causa suele ser aguas arriba — datos malos, drift de init, o LR demasiado alto. Reduce el LR un 2×. - No: el modelo está en plateau. Esto no es divergencia; es "ya estás listo de entrenar". Comprueba las métricas de eval — si la eval también está en plateau, para.
Umbrales numéricos — referencia rápida¶
| Síntoma | Umbral | Primera acción |
|---|---|---|
| loss NaN/Inf | cualquiera | §1 — encontrar la capa originadora |
| spike en un solo paso | loss > μ + 3σ | §2.3 — revisar el panel grad-norm |
| elevación persistente | spike + sin recuperación en 50 pasos | §2.4 — recargar checkpoint |
grad-norm = inf en fp16 |
cualquiera | §3 — loss scaling |
| loss subiendo 200 pasos | monótono | §4 — revisar LR, decay, gradiente |
| cambio de norma de pesos | > 2× en 200 pasos | §4.3 — ajustar decay |
Referencias cruzadas¶
- Fase 18
stability-check.md(comprobaciones a nivel de configuración; hazlas primero). theory/04-loss-spike-postmortem-template.md(el post-mortem que escribes tras una spike).theory/03-three-failure-modes.md(los tres fallos provocados y sus firmas).