Antevisão · Latent Rehearsal
02

Duas arquiteturas,
um arquivo

RoPE, RMSNorm e SwiGLU escritas ao lado das peças de 2019 que elas substituem — e o duelo com mesmo corpus, mesma verba e mesma semente.

Apostila 02 Pré-requisito: apostilas 05 e 06 do curso GPT-2

1 O problema de comparar arquiteturas

Uma peça nova é apresentada nas condições em que foi desenvolvida: outra escala, outros dados, outro orçamento. Um ganho medido lá não se transfere automaticamente para cá. Por isso este capítulo refaz a comparação nas nossas condições — mesmos dados, mesma semente, mesmo número de passos —, sabendo que a comparação justa é trabalhosa e sai cara.

As armadilhas são três, e todas foram evitadas aqui de propósito:

Como cada armadilha foi fechada em architecture.py e train.py.
ArmadilhaO que aconteceNossa trava
Implementações diferentesa peça nova vem de um repositório otimizado, a antiga de um esboçoas duas no mesmo arquivo, mesmo laço de treino, escolhidas por Config(moderno=…)
Contagem de parâmetros diferentea nova ganha porque é maior17,1 M (fiel) contra 16,9 M (moderna) no tamanho pequeno
Orçamento ou dados diferentesa nova viu maismesmo treino.bin, mesmos 700 passos, mesma semente 1337
A regra do projeto

Uma comparação só vale se a única coisa diferente for aquilo que está sendo testado. Por isso o arquivo de dados do duelo foi congelado em model/data/duelo_200mb/ e o script de empacotamento agora recusa sobrescrever sem uma confirmação explícita — um corpus trocado no meio de uma série invalidaria tudo em silêncio.

Onde este arquivo entra na JEPA

A entrevista (apostila 10) responde a uma pergunta que este duelo levanta: a JEPA não é uma alternativa ao Transformer — é uma macro-arquitetura, e o Transformer é o encoder dentro dela. As duas variantes deste arquivo, clássica e moderna, são o mesmo módulo com peças diferentes; o preditor mundo e o custo no latente ficam por cima, iguais para as duas. O duelo abaixo escolhe o encoder; não escolhe o paradigma.

2 As quatro trocas

Posição: wpe aprendida → RoPE

O GPT-2 tem uma tabela wpe com exatamente 1024 linhas — uma por posição possível. É um vetor aprendido, somado ao embedding do token. Funciona, e tem duas consequências duras.

A primeira é a parede. Medimos ela diretamente:

contexto  512  → 29,6 ms/token
contexto 1000  → 34,6 ms/token
contexto 1024  → PAREDE: posição 1024 não existe na wpe (ela tem 1024 linhas: 0 a 1023)

Não é um limite de configuração. É uma matriz que acaba. Para estender o contexto, é preciso criar linhas novas — que nunca foram treinadas.

O RoPE (Su et al., 2021) resolve por construção: em vez de somar um vetor de posição, ele gira os vetores de consulta e chave por um ângulo proporcional à posição. Como o produto escalar entre dois vetores girados depende só da diferença dos ângulos, a atenção passa a enxergar distância relativa — e não há tabela para acabar.

Analogia

A wpe é dar a cada pessoa da fila um crachá com o número dela. Se a fila passa de 1024, acabaram os crachás.

O RoPE é girar cada pessoa por um ângulo proporcional à posição. Duas pessoas separadas por três lugares têm sempre a mesma diferença de ângulo, esteja a dupla no começo ou no fim da fila — e a fila pode ser tão longa quanto se queira.

Custo em parâmetros: a wpe do mini tem 1024 × 256 = 262 144 pesos. O RoPE tem zero — é uma rotação calculada, não aprendida.

Normalização: LayerNorm → RMSNorm

LayerNorm faz duas coisas: centra (subtrai a média) e escala (divide pelo desvio). O RMSNorm (Zhang & Sennrich, 2019) observou que a centragem quase não contribui, e removeu:

RMSNorm(x) = x√mean(x²) + ε · g

Some a subtração da média e some o parâmetro de deslocamento b. Menos contas, metade dos parâmetros de normalização, e nenhuma perda medida na literatura.

MLP: GELU 4× → SwiGLU

O bloco do GPT-2 expande para 4d, aplica GELU e volta. O SwiGLU (Shazeer, 2020) usa duas projeções em paralelo: uma vira sinal, a outra vira portão, e elas se multiplicam.

class SwiGLU(nn.Module):
    def forward(self, x):
        return self.w_down(F.silu(self.w_portao(x)) * self.w_cima(x))

São três matrizes em vez de duas, então a dimensão interna encolhe (de 4d para ⅔·4d) para manter a contagem. O portão deixa a rede aprender quando deixar o sinal passar, em vez de só quanto.

Vieses: todos → nenhum

Llama removeu o termo b de todas as camadas lineares. Menos parâmetros, e na prática nenhuma perda. Adicionamos também QK-norm (Dehghani et al., 2023) — normalizar consulta e chave antes do produto escalar, o que evita explosão dos logits de atenção em treinos longos.

3 O duelo

Tamanho mini (7,4 M), 700 passos, 5,7 M de tokens, mesma semente. Só a arquitetura muda.

Melhor checkpoint de cada rodada — nunca o último, porque a validação oscila entre avaliações.
ArquiteturaValidaçãoPPLno passo
clássica (GPT-2 fiel)5,5391254,45649
moderna5,2616192,79549

−24% de perplexidade, e com 100 passos a menos para chegar ao melhor ponto. Nas quatro medidas de bits/caractere:

bits/caractere, menor é melhor. A moderna fica melhor nos três domínios.
ArquiteturaPortuguêsInglêsCódigoMédia
clássica2,4392,0662,5222,342
moderna2,4062,0382,4052,283
O que este duelo NÃO prova

Uma rodada por arquitetura, uma semente, um tamanho, 5,7 M de tokens — 0,09 época do corpus. Isso mostra que a arquitetura moderna é melhor nesta escala, com este orçamento. Não mostra qual peça contribuiu: RoPE, RMSNorm, SwiGLU e os vieses mudaram todos juntos.

Isolar cada uma exigiria quatro rodadas extras. Está na lista, não no resultado.

Uma correção que o método pegou

Durante o duelo, a validação do modelo clássico subiu entre os passos 649 e 699 (5,539 → 5,577). A leitura imediata foi "começou a sobreajustar". Estava errada.

Com 5,7 M de tokens num corpus de 60,3 M, o modelo viu 0,09 de uma época — nenhum token foi visto duas vezes. Sobreajuste é impossível por construção. O que subiu foi ruído de avaliação somado ao fechamento do cosseno da taxa de aprendizado.

4 O preço de cada peça, em parâmetros

Tamanho pequeno, vocabulário 16 392. Os dois caem quase no mesmo total — que é o que torna a comparação legítima.
PeçaClássicaModerna
Posiçãowpe: 1024×384 = 393 216RoPE: 0
NormalizaçãoLayerNorm: ganho + viésRMSNorm: só ganho
MLP2 matrizes, GELU3 matrizes, SwiGLU (dim ⅔)
Linearescom viéssem viés
Total17,1 M16,9 M

A moderna é ligeiramente menor e ainda ganha. É o argumento mais forte da tabela: não é mais capacidade, é melhor uso da mesma.

5 Como ler o código

Tudo em model/architecture.py, com cada peça moderna escrita imediatamente ao lado da clássica que substitui:

def normalizador(cfg, d):
    return RMSNorm(d) if cfg.moderno else nn.LayerNorm(d)

# no Transformer:
if cfg.moderno:
    self.wpe = None                        # RoPE entra na atenção
    rope = RoPE(cfg.d_k, cfg.n_ctx, cfg.rope_base)
else:
    self.wpe = nn.Embedding(cfg.n_ctx, cfg.d_model)
    rope = None

Ler esse arquivo de cima a baixo é ver a diferença entre 2019 e hoje em ~400 linhas. Era esse o objetivo — mais do que ter as duas opções, ter as duas legíveis lado a lado.

Perguntas em aberto

A arquitetura resolvida, a apostila 03 olha para dentro da tabela de embedding —
e encontra um problema que nenhuma dessas peças modernas conserta.