Atención Más MLP, Repetida
Dos Subcapas
Un bloque transformer contiene exactamente dos subcapas, cada una operando en una secuencia de tokens de forma [batch, seq_len, d_model]:
1. Subcapa de atención multi-cabeza. Los tokens se miran entre sí. La Actividad 68 cubrió esto en detalle. La forma de salida coincide con la forma de entrada.
2. Subcapa MLP de alimentación directa. Cada token se transforma de forma independiente a través de un perceptrón de dos capas. No hay flujo de información entre tokens. La forma de salida coincide con la forma de entrada.
Ambas sublayers preservan la forma [batch, seq_len, d_model]. Esa preservación permite apilar bloques: la salida de la capa N alimenta la entrada de la capa N+1 sin acrobacias de forma.
Qué Contribuye Cada Subcapa
La atención mueve información a través de posiciones: un token en la posición 17 puede extraer información de las posiciones 1 a 16. La MLP transforma información dentro de cada posición: la representación de un token se remodela a través de funciones no lineales aprendidas, pero nunca ve a sus vecinos.
Apilar bloques alterna estas dos operaciones. La atención de la Capa 1 mezcla posiciones. El MLP de la Capa 1 remodela por posición. La atención de la Capa 2 mezcla nuevamente, ahora sobre las representaciones remodeladas. Esta alternancia aumenta el poder expresivo con la profundidad.
La Pila de ANDREA
| Variante | n_layer | n_head | d_model | mlp_dim |
|---|---|---|---|---|
| ANDREA-12M | 6 | 12 | 384 | 1536 |
| ANDREA-120M | 12 | 12 | 768 | 3072 |
| ANDREA-480M | 16 | 24 | 1536 | 6144 |
Observa que mlp_dim = 4 × d_model en toda la familia. Esa proporción se mantiene en casi todos los transformadores modernos. La Sección 3 explica por qué.
Nombrando Las Sublayers
Por qué importan las Conexiones de Salto
El Patrón Residual
Cada subcapas envuelve su cómputo en una conexión residual. La salida suma de nuevo la entrada:
x = x + Attention(LayerNorm(x)) # subcapas de atención
x = x + MLP(LayerNorm(x)) # Subcapa MLP
Dentro de cada subcapa, la función Attention(...) o MLP(...) produce un delta. El bloque no reemplaza la entrada; agrega una corrección aprendida.
Por qué esto importa
Tres razones por las que las conexiones residuales dominan las arquitecturas profundas:
1. Flujo de gradiente. Durante la retropropagación, la adición da a los gradientes un camino directo desde la salida hasta la entrada, obviando la subcapa. Una pila de 12 capas sin residuales perdería la señal de gradiente mucho antes de llegar a los embeddings; con residuales, la magnitud del gradiente se mantiene usable a través de cientos de capas.
2. Inicialización de identidad. En la inicialización, los pesos de la subcapa producen salidas pequeñas. La conexión residual significa que la capa N inicialmente pasa casi sin cambios. El entrenamiento aprende deltas progresivamente desde un punto de partida funcional.
3. Aprendizaje composicional. Cada bloque aprende una refinación, no un reemplazo. La capa 1 podría agregar información posicional. La capa 2 podría agregar estructura sintáctica. La capa 7 podría agregar relaciones semánticas. La corriente residual se acumula.
Normalización de Capa
Antes de cada subcapa, LayerNorm reescala la representación de cada token a media cero y varianza unitaria, luego aplica ganancia y sesgo aprendidos por característica:
y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta
La media y la varianza se calculan a lo largo de la dimensión d_model, por separado para cada token. Dos vectores aprendidos (gamma, beta, cada uno con forma [d_model]) restauran la escala expresiva. La normalización mantiene las activaciones en un rango numéricamente estable; sin ella, pequeñas inestabilidades en el entrenamiento se acumulan en gradientes NaN.
Pre-Norm vs Post-Norm
Una Elección Sutil Pero Crítica
```Dos formas de integrar la normalización de capa en una subcapa residual:
Post-norm (artículo original de 2017):
x = LayerNorm(x + Attention(x))
La normalización de capa se sitúa después de la adición residual. El flujo residual en sí se normaliza en cada capa.
Pre-norm (estándar moderno, usado en ANDREA):
x = x + Attention(LayerNorm(x))
La normalización de capa se encuentra antes de la sub-capa, dentro de la rama residual. El flujo residual permanece sin normalizar; solo la entrada a la sub-capa se reescala.
Por qué ganó Pre-Norm
Post-norm entrena mal sin calentamiento de LR y ajuste cuidadoso de hiperparámetros. Los gradientes explotan en las capas iniciales porque cada normalización de capa desordena el estado acumulado del flujo residual. El artículo original de 2017 usó post-norm con un ajuste extensivo; trabajos posteriores (GPT-2, LLaMA, ANDREA) estandarizaron en pre-norm.
El pre-norm entrena de manera estable. El residual stream se acumula de forma limpia a través de todas las capas; solo las entradas de las sublayers se normalizan para estabilidad numérica. Los transformadores modernos usan pre-norm por defecto, & ANDREA hereda esa elección.
Ecuación del Bloque Final
Combinando residuals, layer norm en posición pre-norm, & ambas sublayers da el bloque completo de ANDREA:
```python
def block_forward(x):
```
x = x + Attention(LayerNorm(x)) # subcapas de atención
x = x + MLP(LayerNorm(x)) # subcapas MLP
return x
Dos sublayers, dos adiciones residuales, dos normalizaciones de capa (nota: cada sublayer tiene su propia normalización de capa; ANDREA-120M tiene 24 normalizaciones de capa en 12 bloques más una final en la salida, total 25). Repetir 12 veces. Ese es el tronco de ANDREA-120M.
Por qué Pre-Norm Estabiliza el Entrenamiento
Dos Capas Lineales, Una Activación
Tres Operaciones
La subcapas MLP es un perceptrón de dos capas con una activación no lineal entre las capas:
def mlp_forward(x):
h = x · W_1 + b_1 # expandir: d_model → mlp_dim
h = GELU(h) # activación no lineal
y = h · W_2 + b_2 # contraer: mlp_dim → d_model
return y
Tres operaciones. Dos lineales, una no lineal. La primera lineal expande el ancho; la segunda contrae de vuelta.
La Relación de Expansión 4×
Los transformadores modernos establecen mlp_dim = 4 × d_model. ANDREA-120M:
d_model = 768
mlp_dim = 4 × 768 = 3072
Forma de W_1 = [768, 3072] # ~2.36M params
Forma de W_2 = [3072, 768] # ~2.36M params
Parámetros MLP por bloque = 4.72M (ignorando sesgos)
Dos MLPs se sitúan entre cada par de subcapas de atención (uno por bloque). Doce bloques × 4.72M ≈ 56.6M parámetros MLP totales en ANDREA-120M, aproximadamente la mitad de todos los parámetros.
Por qué 4×
La relación 4× surgió empíricamente. Relaciones más pequeñas reducen la capacidad del modelo. Relaciones más grandes producen rendimientos decrecientes por parámetro gastado. A lo largo de décadas de búsqueda de arquitecturas, el 4× se ha mantenido; aparece en GPT, BERT, T5, LLaMA y ANDREA.
Trabajos recientes (PaLM, Chinchilla) encontraron que los mecanismos de compuerta (SwiGLU) pueden usar una expansión 8/3× con capacidad comparable a menor costo; ANDREA se mantiene con GELU clásico + 4× por simplicidad.
GELU: Una Activación Suave
Qué Calcula GELU
GELU (Gaussian Error Linear Unit) es la activación estándar entre capas MLP en transformadores modernos. Su fórmula:
GELU(x) = x · Φ(x)
Φ(x) es la función de distribución acumulada de la normal estándar: la probabilidad de que una variable aleatoria gaussiana estándar caiga en o por debajo de x. Calculada numéricamente:
Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))
Comportamiento Por Región
- Para x positivo grande: Φ(x) ≈ 1, por lo que GELU(x) ≈ x. Como ReLU.
- Para x negativo grande: Φ(x) ≈ 0, por lo que GELU(x) ≈ 0. Como ReLU.
- Cerca de x = 0: Φ(x) ≈ 0.5, por lo que GELU(0) = 0 exactamente. Transición suave a través del origen.
A diferencia de ReLU, GELU permite que algunos valores de entrada negativos pasen, ponderados por Φ(x). Una entrada negativa pequeña aún contribuye con una salida negativa pequeña, solo menos que la entrada completa lo haría.
Por qué GELU superó a ReLU
Empíricamente, los transformadores entrenados con GELU alcanzan una pérdida menor que los transformadores entrenados con ReLU con el mismo número de parámetros. La suavidad alrededor de cero importa: el corte abrupto de ReLU en cero produce discontinuidades en los gradientes; la curva suave de GELU proporciona gradientes más limpios para la retropropagación.
El motor de entrenamiento de ANDREA microgpt_cuda.cu incluye un kernel CUDA de GELU escrito a mano. El kernel usa la aproximación tanh anterior; las GPUs modernas incluyen tanh como una operación de una sola instrucción.
Cálculo de parámetros MLP
Doce Bloques se Compone en ANDREA-120M
Del Bloque al Modelo
La pasada completa hacia adelante de ANDREA-120M:
def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer): # 12 bloques
x = block_forward(x) # attention + MLP con residuales
x = LayerNorm(x) # norma final
logits = x · token_embed.T # pesos compartidos para proyección de salida
return logits
Seis líneas. El grueso vive dentro de block_forward, llamado doce veces. Los embeddings inician el pipeline; el unembedding atado (la misma matriz usada para la búsqueda de entrada, transpuesta para la proyección de salida) lo finaliza.
Profundidad Como Composición
Cada bloque lee el residual stream, calcula un delta y lo suma de nuevo. Después de doce pasadas, el stream contiene contribuciones acumuladas de cada bloque. Internamente, las capas tienden a especializarse:
- Capas tempranas (1-3): patrones sintácticos, estructura posicional
- Capas medias (4-8): relaciones entre palabras, límites de frases
- Capas tardías (9-12): contenido semántico, recuerdo factual
Esta especialización surge de la presión del entrenamiento, no de elecciones arquitectónicas. El mismo diseño de bloque uniforme produce capas especializadas cuando se entrena en lenguaje.
Parámetros Totales del Bloque
| Componente | Por bloque | En 12 bloques |
|---|---|---|
| Proyecciones de atención (4×W) | 2.36M | 28.3M |
| Pesos MLP (W_1 + W_2) | 4.72M | 56.6M |
| Normalizaciones de capa (gamma, beta) | ~3K (insignificante) | ~37K |
| Total por bloque | ~7.1M | ~85M |
85M parámetros en el tronco. Agrega ~13M en embeddings de tokens (8449 vocab × 768 d_model × 2 para entrada/salida atada) más una normalización de capa final, & el conteo de parámetros de ANDREA-120M llega a aproximadamente 120M. El diseño del bloque representa dos tercios; los embeddings el resto.