Skip to content

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

  1. El run arrancó sano. Algo se torció después del paso \(K\).
  2. Ten abiertos dashboard.html y experiments/<run>/manifest.json.
  3. 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).