English · Español
Lab 03 — Profundidad con residuales: MLP de 50 capas, con y sin¶
Objetivo: ver la autopista de gradientes en acción. Un MLP de 50 capas sin conexiones residuales (residual connections) no consigue entrenar; la misma arquitectura con residuales entrena bien.
Tiempo estimado: 90–120 minutos.
Prerrequisito: labs 01 y 02 commiteados;
theory/03-residuals.mdleído.
Qué produces¶
Un directorio experiments/10-residual-depth/ que contiene:
train.py— script de entrenamiento con flag--use-residual.losses.json— dos trayectorias.grad_norms.json— norma del gradiente en la capa 1 a lo largo del entrenamiento, para ambas variantes.loss_curves.png— dos curvas.grad_norm.png— gráfico log-y de la norma del gradiente en la capa 1 a lo largo del entrenamiento.manifest.json.README.md.
El montaje¶
Un MLP de 50 capas, oculta 256, activación GeLU, inicialización Kaiming (con ganancia gelu), Pre-LN RMSNorm, sobre los mismos datos de juguete de los labs 01–02.
Dos ejecuciones:
- Sin residual. Cada bloque:
x → RMSNorm → Linear → GeLU → Linear → siguiente. - Con residual. Cada bloque:
x → RMSNorm → Linear → GeLU → Linear → (suma x) → siguiente.
Por lo demás idénticas: misma inicialización, mismo optimizador, mismos datos, misma semilla.
Esperado:
- Sin residual: la pérdida desciende durante ~50 pasos, luego se estanca. La norma del gradiente en la capa 1 cae por debajo de
1e-7en ~100 pasos (desvanecida). - Con residual: descenso suave de la pérdida en todo momento. La norma del gradiente en la capa 1 se mantiene en
[1e-3, 1e-1].
Esta es la prueba empírica del argumento de la autopista de gradientes de la teoría 03 en la máquina de Borja.
TODOs¶
Bloque A — implementa el wrapper de residual¶
- Escribe
src/minigrad/nn/residual.pycon un móduloResidual(f)que calculey = x + f(x). - Gradcheck del residual.
- Verifica que
Residual(lambda x: 0 * x)es exactamente la identidad.
Bloque B — construye el MLP de 50 capas¶
- 50 bloques. Cada bloque:
Pre-LN RMSNorm → Linear(h, 4h) → GeLU → Linear(4h, h). - Envuelve cada bloque en
Residualpara la variante con-residual; déjalo sin envolver para la variante sin-residual. - Inicialización Kaiming escalada para la ganancia efectiva de GeLU (~1.7).
Bloque C — seguimiento de la norma del gradiente¶
- Tras
loss.backward(), capturanp.linalg.norm(model.layers[0].weight.grad)y regístralo engrad_norms.json. - Hazlo en cada paso (o cada 10 pasos si es demasiado lento).
Bloque D — dos ejecuciones¶
- Ejecuta sin residual. Guarda losses + grad_norms.
- Ejecuta con residual. Guarda losses + grad_norms.
- Misma semilla entre ejecuciones.
Bloque E — gráfico¶
- Curvas de pérdida (2 líneas).
- Gráfico de norma de gradiente en la capa 1, eje log-y, 2 líneas.
- Anota el paso en el que la norma del gradiente de la ejecución sin-residual cae por debajo de
1e-7.
Bloque F — interpreta¶
En README.md:
- ¿Entrenó algo la ejecución sin-residual? Si sí, ¿cuál es la pendiente de su pérdida en los últimos 200 pasos?
- ¿En qué paso se desvaneció efectivamente el gradiente de la capa 1? Cita un número.
- La norma del gradiente de la ejecución con-residual se mantiene aproximadamente en qué rango? Coteja con la predicción de la teoría 03.
- ¿Qué pasaría con 100 capas sin residuales? (Predice; no es necesario ejecutar.)
- Supón que inicializas el último Linear de cada bloque con
weight = 0. Predice la salida inicial del modelo con-residual. ¿Por qué podría ser útil esta inicialización en redes muy profundas?
Restricciones¶
- Misma semilla de datos, misma configuración de optimizador, misma arquitectura salvo por el wrap
Residual. - Sigue la norma del gradiente sólo de la capa 1. Seguir todas las capas hace explotar el JSON.
- Un único hilo, governor
performance. mypy --strictsobresrc/minigrad/nn/residual.py.
Condiciones de parada¶
Hecho cuando:
- Los siete archivos están commiteados.
- El gráfico de norma de gradiente muestra el gradiente en la capa 1 de la ejecución sin-residual cayendo por debajo de
1e-7en ~100 pasos. - La ejecución con-residual entrena suavemente hasta una pérdida baja.
- El README responde las cinco preguntas del Bloque F.
Trampas¶
- La norma del gradiente no se desvanece sin residuales. Podría ser que 50 capas no basten con tu elección concreta de activación. Prueba 100. O la colocación de tu norm está mal — Pre-LN antes de toda la cadena (no por bloque) efectivamente mantiene vivos los gradientes.
- El residual hace la pérdida peor. Casi seguro un bug de inicialización — la salida interna del bloque residual está dominando a la identidad. Reduce la escala de inicialización del bloque interno.
- Las dos ejecuciones producen pérdidas idénticas. Lo más probable es que el flag
--use-residualno haga lo que crees. Añade unprint("Using residual:", use_residual)al inicio. - El JSON de norma de gradiente es enorme. Registra cada 10 pasos en lugar de cada paso. El gráfico sigue siendo informativo.
- Explosión de memoria con 50 capas. Oculta 256 × expansión 4× × 50 bloques son ~13M parámetros. Deberían ser ~50 MiB. Si tienes OOM, tienes un leak (p. ej., no estás limpiando grafos de cómputo intermedios).
Pista de último recurso¶
Si llevas 90 minutos y el residual no ayuda: escribe un test de 5 líneas que construya un Residual(lambda x: -x) y verifique Residual.forward(x) == 0. Si eso falla, tu wrapper de residual está roto. Si pasa, tu montaje de entrenamiento es el problema (p. ej., el residual se está sumando fuera del bloque en vez de dentro).
Cuándo consultar solutions/¶
Tras los siete archivos. Solución: solutions/03-residual-depth-ref.md (fase abierta).
Secuencia de labs de la Fase 10 completa. Siguiente fase: docs/phase-11-tokenization-bpe/.