English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

visitante
1 / ?

Atenção Mais MLP, Repetida

Estrutura Pré-Norm do Bloco Transformer


Duas Subcamadas

Um bloco transformer contém exatamente duas subcamadas, cada uma operando em uma sequência de tokens com forma [batch, seq_len, d_model]:


1. Camada de atenção multi-head. Os tokens olham uns para os outros. A Atividade 68 cobriu isso em detalhes. O formato de saída corresponde ao formato de entrada.

2. Camada MLP feed-forward. Cada token é transformado independentemente por meio de um perceptron de duas camadas. Não há fluxo de informação entre tokens. O formato de saída corresponde ao formato de entrada.


Ambas as sublayers preservam o formato [batch, seq_len, d_model]. Essa preservação permite empilhar blocos: a saída da camada N alimenta a entrada da camada N+1 sem acrobacias de formato.


O Que Cada Sublayer Contribui

A atenção move informação entre posições: um token na posição 17 pode puxar informação das posições 1 a 16. A MLP transforma informação dentro de cada posição: a representação de um token é remodelada por funções não lineares aprendidas, mas nunca vê seus vizinhos.


Empilhar blocos alterna essas duas operações. A atenção da Camada 1 mistura posições. O MLP da Camada 1 remodela por posição. A atenção da Camada 2 mistura novamente, agora sobre as representações remodeladas. Essa alternância aumenta o poder expressivo com a profundidade.


A Pilha da ANDREA


Varianten_layern_headd_modelmlp_dim
ANDREA-12M6123841536
ANDREA-120M12127683072
ANDREA-480M162415366144

Note que mlp_dim = 4 × d_model em toda a família. Essa proporção se mantém em quase todos os transformers modernos. A Seção 3 explica o porquê.

Nomeando as Sublayers

Um bloco transformer contém duas sublayers. Nomeie-as em ordem, & para cada uma, indique se ela move informação entre posições (token-para-token) ou transforma informação dentro de uma única posição (independente por token). Uma frase por sublayer.

Por que as Conexões de Pulo São Importantes

O Padrão Residual

Cada subcamada envolve seu cálculo em uma conexão residual. A saída adiciona de volta a entrada:


x = x + Attention(LayerNorm(x))     # subcamada de atenção
x = x + MLP(LayerNorm(x))           # subcamada MLP

Dentro de cada subcamada, a função Attention(...) ou MLP(...) produz um delta. O bloco não substitui a entrada; ele adiciona uma correção aprendida.


Por Que Isso Importa

Três razões pelas quais as conexões residuais dominam arquiteturas profundas:


1. Fluxo de gradiente. Durante a retropropagação, a adição dá aos gradientes um caminho direto da saída de volta para a entrada, contornando a subcamada. Uma pilha de 12 camadas sem resíduos perderia o sinal de gradiente muito antes de alcançar os embeddings; com resíduos, a magnitude do gradiente permanece utilizável através de centenas de camadas.


2. Inicialização de identidade. Na inicialização, os pesos da subcamada produzem saídas pequenas. A conexão residual significa que a camada N inicialmente passa quase inalterada. O treinamento aprende deltas progressivamente a partir de um ponto de partida funcional.


3. Aprendizado composicional. Cada bloco aprende uma refinamento, não uma substituição. A Camada 1 pode adicionar informações posicionais. A Camada 2 pode adicionar estrutura sintática. A Camada 7 pode adicionar relações semânticas. O fluxo residual acumula.


Normalização de Camada

Antes de cada subcamada, LayerNorm reescala a representação de cada token para média zero e variância unitária, depois aplica ganho e viés aprendidos por feature:


y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta

A média & variância são calculadas ao longo da dimensão d_model, separadamente para cada token. Dois vetores aprendidos (gamma, beta, cada um com formato [d_model]) restauram a escala expressiva. A normalização mantém as ativações em uma faixa numericamente estável; sem ela, pequenas instabilidades no treinamento se acumulam em gradientes NaN.

Pré-Norm vs Pós-Norm

Uma Escolha Sutil Mas Crítica

Duas maneiras de conectar a normalização de camada em uma subcamada residual:


Pós-norm (artigo original de 2017):

x = LayerNorm(x + Attention(x))

A normalização de camada fica após a adição residual. O próprio fluxo residual é normalizado em cada camada.


Pré-norm (padrão moderno, usado no ANDREA):

x = x + Attention(LayerNorm(x))

A normalização de camada fica antes da subcamada, dentro do ramo residual. O fluxo residual permanece sem normalização; apenas a entrada da subcamada é reescalada.


Por que o Pré-Norm Venceu

O pós-norm treina mal sem aquecimento de LR e ajuste cuidadoso de hiperparâmetros. Os gradientes explodem nas camadas iniciais porque cada normalização de camada embaralha o estado acumulado do fluxo residual. O artigo original de 2017 usou pós-norm com ajuste extensivo; trabalhos subsequentes (GPT-2, LLaMA, ANDREA) padronizaram no pré-norm.


O treinamento pré-norm é estável. O fluxo residual acumula de forma limpa em todas as camadas; apenas as entradas das subcamadas são normalizadas para estabilidade numérica. Transformers modernos usam pré-norm por padrão, e o ANDREA herda essa escolha.


Equação Final do Bloco

Combinando resíduos, layer norm na posição pré-norm e ambas as sublayers, obtemos o bloco completo do ANDREA:


```python
def block_forward(x):
```
x = x + Attention(LayerNorm(x))   # subcamada de atenção
x = x + MLP(LayerNorm(x))         # subcamada MLP
return x

Duas subcamadas, duas adições residuais, duas normalizações de camada (nota: cada subcamada tem sua própria normalização de camada; ANDREA-120M tem 24 normalizações de camada em 12 blocos mais uma final na saída, totalizando 25). Repita 12 vezes. Essa é o tronco do ANDREA-120M.

Por que o Pre-Norm Estabiliza o Treinamento

O ANDREA usa pre-norm: `x = x + Attention(LayerNorm(x))`. Compare com post-norm: `x = LayerNorm(x + Attention(x))`. Dê um motivo de uma perspectiva de fluxo de gradiente pelo qual o pre-norm treina de forma mais estável que o post-norm em pilhas profundas. Referencie o fluxo residual em sua resposta.

Duas Camadas Lineares, Uma Ativação

Três Operações

A sublayer MLP é um perceptron de duas camadas com uma ativação não linear entre as camadas:


def mlp_forward(x):
h = x · W_1 + b_1        # expandir: d_model → mlp_dim
h = GELU(h)              # ativação não linear
y = h · W_2 + b_2        # contrair: mlp_dim → d_model
return y

Três operações. Duas lineares, uma não linear. A primeira linear expande a largura; a segunda contrai de volta.


A Razão de Expansão 4×

Transformers modernos definem mlp_dim = 4 × d_model. ANDREA-120M:


d_model = 768
mlp_dim = 4 × 768 = 3072
Formato de W_1 = [768, 3072]      # ~2.36M params
Formato de W_2 = [3072, 768]      # ~2.36M params
Parâmetros MLP por bloco = 4.72M (ignorando biases)

Duas MLPs ficam entre cada par de subcamadas de atenção (uma por bloco). Doze blocos × 4.72M ≈ 56.6M parâmetros MLP no total em ANDREA-120M, aproximadamente metade de todos os parâmetros.


Por que 4×

A proporção 4× surgiu empiricamente. Proporções menores reduzem a capacidade do modelo. Proporções maiores produzem retornos decrescentes por parâmetro gasto. Ao longo de décadas de busca por arquitetura, 4× se manteve; ela aparece em GPT, BERT, T5, LLaMA e ANDREA.


Trabalhos recentes (PaLM, Chinchilla) descobriram que mecanismos de gating (SwiGLU) podem usar expansão 8/3× com capacidade comparável a um custo menor; ANDREA mantém o clássico GELU + 4× por simplicidade.

GELU: Uma Ativação Suave

O que o GELU Calcula

GELU (Gaussian Error Linear Unit) é a ativação padrão entre as camadas MLP em transformadores modernos. Sua fórmula:


GELU(x) = x · Φ(x)

Φ(x) é a função de distribuição acumulada do normal padrão: a probabilidade de que uma variável aleatória gaussiana padrão caia em ou abaixo de x. Calculada numericamente:


Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))

Comportamento Por Região

- Para x positivo grande: Φ(x) ≈ 1, logo GELU(x) ≈ x. Como ReLU.

- Para x negativo grande: Φ(x) ≈ 0, logo GELU(x) ≈ 0. Como ReLU.

- Perto de x = 0: Φ(x) ≈ 0.5, logo GELU(0) = 0 exatamente. Transição suave através da origem.


Diferente do ReLU, o GELU permite que alguns valores negativos passem, ponderados por Φ(x). Um pequeno valor negativo ainda contribui com uma pequena saída negativa, apenas menor que a entrada completa seria.


Por que o GELU Superou o ReLU

Empiricamente, transformers treinados com GELU alcançam perda menor do que transformers treinados com ReLU com o mesmo número de parâmetros. A suavidade ao redor de zero importa: o corte abrupto do ReLU em zero produz descontinuidades no gradiente; a curva suave do GELU fornece gradientes mais limpos para a retropropagação.


O motor de treinamento da ANDREA microgpt_cuda.cu inclui um kernel CUDA GELU escrito à mão. O kernel usa a aproximação tanh acima; GPUs modernas incluem tanh como uma operação de instrução única.

Calculando Parâmetros do MLP

ANDREA-120M tem `d_model=768`, `mlp_dim=3072` e `n_layer=12`. Calcule o total de parâmetros nas matrizes de peso do MLP (`W_1` e `W_2`) em todos os 12 blocos. Ignore os biases. Mostre sua aritmética. Em seguida, indique qual fração dos ~120M parâmetros totais da ANDREA-120M isso representa (arredonde para uma casa decimal).

Doze Blocos Compõem o ANDREA-120M

Do Bloco ao Modelo

Passada forward completa do ANDREA-120M:


def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer):       # 12 blocos
x = block_forward(x)               # attention + MLP com resíduos
x = LayerNorm(x)                       # normalização final
logits = x · token_embed.T             # pesos compartilhados para projeção de saída
return logits

Seis linhas. O grosso vive dentro de block_forward, chamado doze vezes. Embeddings iniciam o pipeline; unembedding amarrado (a mesma matriz usada para lookup de entrada, transposta para projeção de saída) o finaliza.


Profundidade Como Composição

Cada bloco lê o residual stream, computa um delta e o adiciona de volta. Após doze passadas, o stream contém contribuições acumuladas de cada bloco. Internamente, as camadas tendem a se especializar:


- Camadas iniciais (1-3): padrões sintáticos, estrutura posicional

- Camadas médias (4-8): relações entre palavras, limites de frases

- Camadas finais (9-12): conteúdo semântico, recall factual


Essa especialização emerge da pressão de treinamento, não de escolhas arquiteturais. O mesmo design de bloco uniforme produz camadas especializadas quando treinado em linguagem.


Parâmetros Totais do Bloco


ComponentPor blocoEm 12 blocos
Projeções de Atenção (4×W)2.36M28.3M
Pesos MLP (W_1 + W_2)4.72M56.6M
Normalizações de camada (gamma, beta)~3K (negligível)~37K
Total por bloco~7.1M~85M

85M parâmetros no tronco. Adicione ~13M em embeddings de tokens (8449 vocabulário × 768 d_model × 2 para entrada/saída atada) mais uma normalização de camada final, & o número de parâmetros do ANDREA-120M chega a aproximadamente 120M. O design do bloco representa dois terços; embeddings o resto.

Rastreando Um Token Através De Um Bloco

Um vetor de token de 768-dim entra no bloco 7 do ANDREA-120M. Descreva o que acontece com ele dentro do bloco (na estrutura pre-norm). Mencione: ambas as normalizações de camada, ambas as subcamadas, ambas as adições residuais, & a forma final. Indique pelo menos um lugar onde o fluxo residual permanece intocado & um lugar onde ele é modificado.