12. Transformers
O capítulo anterior construiu um mecanismo. Um Transformer é o que se obtém quando se decide que esse mecanismo é suficiente — que dá para jogar fora a recorrência e a convolução e ficar só com atenção, mais a menor quantidade possível de andaime em volta1.
O andaime acaba sendo curto: um MLP por posição, uma conexão residual, uma normalização. Esse bloco, repetido, é a arquitetura inteira — do GPT, do Llama, do ViT, do Whisper, dos backbones de difusão do capítulo 22. Quase nada mais em IA moderna tem essa propriedade de ser um único desenho usado em toda parte.
Este capítulo tem duas tarefas. A primeira é mostrar o que o bloco faz. A segunda é mais útil e menos frequente: mostrar quais partes do desenho de 2017 continuam ali e quais foram silenciosamente substituídas, porque o diagrama do artigo original não é o bloco que você escreveria hoje.
Roteiro do capítulo
- O caminho completo — do texto que entra à probabilidade da próxima palavra.
- Dentro de um bloco — atenção para conversar, MLP para pensar, residual para acumular.
- O que mudou desde 2017 — pré-LN, RMSNorm, SwiGLU e companhia.
- Qual família venceu — encoder, decoder ou os dois.
- Escala — por que ficar maior funciona e maior em quê.
Do texto à próxima palavra: o caminho completo
Antes de abrir o bloco, vale ver onde ele fica. Um modelo de linguagem recebe "o gato subiu no" e devolve uma probabilidade para cada token do vocabulário ser o próximo:
flowchart TB
T["'o gato subiu no'<br/>→ índices [12, 873, 4051, 88]"] --> E["embedding<br/>(n × d)"]
E --> B1["bloco 1"] --> B2["bloco 2"] --> BD["…"] --> BL["bloco L"]
BL --> N["norm final"]
N --> U["unembedding: d → V<br/>(n × V)"]
U --> P["softmax na última posição<br/>telhado 0,41 · muro 0,18 · sofá 0,09 · …"] (Os índices e as probabilidades são ilustrativos.) Três coisas para reparar:
- A forma não muda dentro da pilha. O embedding transforma os \(n\) tokens numa matriz \(n \times d\) e cada bloco recebe \(n \times d\) e devolve \(n \times d\). É isso que permite empilhar 32 ou 126 blocos iguais. Só a última camada volta para o tamanho do vocabulário \(V\).
- Cada posição produz uma previsão. A linha \(i\) da saída é a distribuição do token \(i+1\). Na geração, só a última linha interessa; no treino, todas contam — a máscara causal do capítulo anterior garante que nenhuma delas veja a resposta.
- Gerar é repetir. Sorteie um token da distribuição, anexe-o à entrada e rode de novo. O cache de KV é o que impede esse "rode de novo" de recalcular tudo.
Dentro de um bloco: conversar e depois pensar
Um bloco tem duas subcamadas e cada uma tem um papel bem definido:
- A atenção é a única parte em que os tokens trocam informação. É uma reunião: cada token pergunta, os outros respondem e ele sai com um resumo do que ouviu.
- O MLP (ou feed-forward) processa cada token sozinho, com os mesmos pesos para todas as posições. É o trabalho individual depois da reunião: pegar o que chegou e transformar em algo útil. É também onde fica a maior parte do "conhecimento" armazenado do modelo.
Nenhuma das duas substitui o vetor do token. Cada uma calcula uma correção que é somada a ele pela conexão residual:
flowchart TB
X["x (n × d)"] --> N1["Norm"] --> A["atenção<br/>(tokens conversam)"] --> P1(("+"))
X -- "residual" --> P1
P1 --> N2["Norm"] --> F["MLP<br/>(cada token sozinho)"] --> P2(("+"))
P1 -- "residual" --> P2
P2 --> Y["y (n × d)"] O script abaixo passa uma frase de 5 tokens por um bloco completo, pequeno o bastante para ler (\(d = 8\)), imprime a forma em cada etapa e depois conta os parâmetros do mesmo bloco na largura de um modelo real:
"""Um bloco pré-LN inteiro em numpy: as formas em cada etapa e onde os parâmetros moram.
Primeiro uma frase de 5 tokens atravessa um bloco pequeno (d = 8) e cada etapa imprime sua forma —
o fluxo residual entra (n, d) e sai (n, d). Depois a mesma conta de parâmetros para d = 4096, a
largura de um modelo classe 8B, com GQA e SwiGLU.
"""
import numpy as np
rng = np.random.default_rng(0)
def rmsnorm(x, eps=1e-6):
return x / np.sqrt((x ** 2).mean(-1, keepdims=True) + eps)
def softmax(s):
e = np.exp(s - s.max(-1, keepdims=True))
return e / e.sum(-1, keepdims=True)
def silu(x):
return x / (1 + np.exp(-x))
def swiglu_width(d):
return round(8 * d / 3 / 256) * 256 if d >= 256 else 8 * d // 3
def params(d, h, h_kv):
dh = d // h
attn = d * d + 2 * d * h_kv * dh + d * d # W_Q, W_K e W_V (GQA), W_O
ffn = 3 * d * swiglu_width(d) # W_1, W_3, W_2
return attn, ffn, 2 * d # duas RMSNorm, um ganho cada
n, d, h, h_kv = 5, 8, 2, 1
dh, f = d // h, swiglu_width(d)
Wq, Wk, Wv, Wo = (rng.normal(size=s) / np.sqrt(d) for s in [(d, d), (d, h_kv * dh), (d, h_kv * dh), (d, d)])
W1, W3, W2 = rng.normal(size=(d, f)) / np.sqrt(d), rng.normal(size=(d, f)) / np.sqrt(d), rng.normal(size=(f, d)) / np.sqrt(f)
def show(nome, t):
print(f" {nome:<34}{str(t.shape):>10}")
x = rng.normal(size=(n, d))
print(f"n = {n} tokens, d = {d}, {h} heads de query, {h_kv} head de K/V")
show("x (fluxo residual, entrada)", x)
u = rmsnorm(x)
q = (u @ Wq).reshape(n, h, dh).transpose(1, 0, 2)
k = (u @ Wk).reshape(n, h_kv, dh).transpose(1, 0, 2).repeat(h // h_kv, axis=0) # GQA: K/V repetidos
v = (u @ Wv).reshape(n, h_kv, dh).transpose(1, 0, 2).repeat(h // h_kv, axis=0)
show("Q, por head", q)
show("K, V (1 head, repetido)", k)
causal = np.triu(np.full((n, n), -np.inf), 1)
A = softmax(q @ k.transpose(0, 2, 1) / np.sqrt(dh) + causal)
show("A = softmax(QKᵀ/√d_h + M)", A)
o = (A @ v).transpose(1, 0, 2).reshape(n, d) @ Wo
x = x + o
show("x + atenção", x)
u = rmsnorm(x)
g = silu(u @ W1) * (u @ W3)
show("SwiGLU, camada oculta", g)
x = x + g @ W2
show("x + MLP (saída do bloco)", x)
print("\nd = 4096, 32 heads de query, 8 de K/V:")
attn, ffn, norm = params(4096, 32, 8)
total = attn + ffn + norm
for nome, p in [("atenção", attn), ("MLP (SwiGLU)", ffn), ("normalização", norm)]:
print(f" {nome:<14}{p / 1e6:8.1f} M {100 * p / total:4.0f}%")
print(f" {'bloco':<14}{total / 1e6:8.1f} M")
print(f" × 32 blocos {32 * total / 1e9:8.2f} B")
n = 5 tokens, d = 8, 2 heads de query, 1 head de K/V
x (fluxo residual, entrada) (5, 8)
Q, por head (2, 5, 4)
K, V (1 head, repetido) (2, 5, 4)
A = softmax(QKᵀ/√d_h + M) (2, 5, 5)
x + atenção (5, 8)
SwiGLU, camada oculta (5, 21)
x + MLP (saída do bloco) (5, 8)
d = 4096, 32 heads de query, 8 de K/V:
atenção 41.9 M 24%
MLP (SwiGLU) 135.3 M 76%
normalização 0.0 M 0%
bloco 177.2 M
× 32 blocos 5.67 B
O fluxo residual entra \(5 \times 8\) e sai \(5 \times 8\); só as etapas internas mudam de forma. A matriz de atenção é \(5 \times 5\) por cabeça (cada token contra cada token) e a camada oculta do MLP é mais larga que \(d\). Na largura real, 32 blocos somam 5,7B de parâmetros; com a tabela de embedding e a camada de saída (\(2 \times 128\text{k} \times 4096 \approx 1\text{B}\)) chega-se perto de 7B. O Llama 3 8B usa um MLP ainda mais largo (14336 em vez de 11008), o que fecha os 8B.
Verifique você mesmo — o que sobra se você tirar a atenção de todos os blocos?
Cada token passaria pela pilha inteira sem nunca saber quem são os vizinhos: o MLP só vê a própria posição. A previsão do próximo token dependeria só do token atual — depois de "no", o modelo diria sempre a mesma coisa, fosse a frase sobre um gato ou sobre um banco. A atenção é a única rota de contexto do bloco.
O bloco e as seis coisas que mudaram
O bloco que o script acima montou é o de 2026. O de 2017 era diferente em seis pontos:
| Peça | 2017 | 2026 | Por quê, em uma linha |
|---|---|---|---|
| Posição da norma | depois da soma (pós-LN) | antes da subcamada (pré-LN) | deixa o caminho residual limpo |
| Normalizador | LayerNorm | RMSNorm | mesma qualidade, menos contas |
| MLP | ReLU, 2 matrizes, \(4d\) | SwiGLU, 3 matrizes, \(\tfrac{8}{3}d\) | um portão multiplicativo que ganha sempre |
| Atenção | MHA | GQA | cache de KV 4× menor |
| Posição | senoidal, na entrada | RoPE, em Q e K | posição relativa e contexto extensível |
| Viés (bias) | em todas as lineares | nenhum | não faz falta e custa memória |
Configure-o. O padrão é o que um modelo lançado este ano parece; mude todos os controles para a primeira opção e você obtém o artigo de 2017, exatamente.
Duas dessas mudanças valem mais do que as outras.
Pré-LN: onde a normalização fica
O bloco de 2017 normaliza depois da soma residual:
o que significa que o caminho residual passa por um normalizador em cada camada. O gradiente deixa de ter a rota identidade limpa que o capítulo 9 identificou como o ponto inteiro de uma conexão residual e a consequência é concreta: Transformers pós-LN não treinam sem warmup na taxa de aprendizado e ficam mais difíceis de treinar quanto mais profundos são5.
Mover a normalização para dentro do ramo resolve:
Agora o fluxo residual fica intocado da perda até a camada 1. Dá para ver isso numa linha: desenrolando a recorrência, a saída de uma pilha pré-LN de \(L\) blocos é a entrada mais a soma de todas as correções,
e esse \(I\) é o caminho expresso: o gradiente chega à primeira camada inteiro, pelo menos por essa parcela, não importa quantos blocos existam no meio. No pós-LN cada bloco termina num \(\text{Norm}\) e a soma nunca aparece sozinha — não há \(I\) limpo. Esse único rearranjo é a razão pela qual pilhas de 100 camadas são banais e é por isso que os cronogramas de warmup dos tutoriais antigos parecem superstição — eles compensavam um projeto que foi abandonado.
O fluxo residual é o objeto com que se deve pensar
O pré-LN sugere um modelo mental melhor do que "uma pilha de camadas". Existe um fluxo residual por posição, atravessando toda a profundidade do modelo e cada bloco lê dele, computa algo e soma o resultado de volta. Nada sobrescreve; tudo se acumula. A atenção move informação entre fluxos; o MLP transforma informação dentro de um. É sobre esse enquadramento que a interpretabilidade mecanicista é construída e ele explica de relance por que se pode apagar ou reordenar alguns blocos de um modelo treinado com surpreendentemente pouco estrago.
Normalização e a camada feed-forward
O RMSNorm6 abandona a subtração da média do LayerNorm e mantém só o reescalonamento:
Um exemplo com \(x = [3,\ 4]\) mostra a diferença. O LayerNorm subtrai a média (3,5) e divide pelo desvio padrão (0,5): sai \([-1,\ 1]\). O RMSNorm só divide pela raiz da média dos quadrados, \(\sqrt{(9+16)/2} \approx 3{,}54\): sai \([0{,}85;\ 1{,}13]\). Os dois colocam o vetor numa escala padrão, que é o que importa para a camada seguinte; centrar se mostrou dispensável.
Uma redução em vez de duas, sem viés e sem custo mensurável de qualidade. Universal desde o Llama.
O MLP também mudou. O original são duas matrizes com uma ReLU no meio e largura oculta 4×. O moderno é o SwiGLU7 — três matrizes, em que um ramo abre a passagem do outro:
Leia a fórmula como dois ramos lado a lado: \(xW_3\) propõe um conteúdo e \(\text{Swish}(xW_1)\) decide, dimensão por dimensão, quanto dele passa — perto de zero fecha, valores maiores abrem ou amplificam. É um regulador que a própria entrada controla, o mesmo princípio das portas do LSTM.
A largura oculta cai para \(\tfrac{8}{3}d\) para que a contagem de parâmetros fique igual e o portão multiplicativo compra um ganho de qualidade consistente, ainda que modesto. Sobreviveu a todas as ablações desde 2020.
Onde os parâmetros de fato estão
No painel acima, entre dois terços (com MHA) e três quartos (com GQA, como na saída do script) de cada bloco é a camada feed-forward, não a atenção. A atenção leva a atenção; o MLP guarda os parâmetros — e é o MLP que a Mixture-of-Experts substitui para crescer um modelo sem crescer seu custo por token (capítulo 15).
Encoder, decoder e qual sobreviveu
O modelo de 2017 era um encoder–decoder para tradução: um encoder que vê a origem inteira de forma bidirecional, um decoder que gera causalmente e lê o encoder por atenção cruzada. Três famílias nasceram dele e seus destinos divergiram muito.
| Família | Atenção | Treinada por | Situação em 2026 |
|---|---|---|---|
| Só encoder — BERT2 | Bidirecional | Previsão de tokens mascarados | Viva, num nicho específico: embeddings, busca e rerankers, onde você codifica uma vez e compara barato. O ModernBERT8 o reconstruiu com RoPE, GLU e contexto de 8k. Ele não gera. |
| Encoder–decoder — T5, BART | Bidirecional + causal + cruzada | Denoising / seq2seq | Largamente deslocada para texto. O padrão sobrevive onde as duas modalidades são genuinamente diferentes — fala (Whisper) e a atenção cruzada que injeta texto num modelo de imagem. |
| Só decoder — GPT, Llama, Claude | Causal | Previsão do próximo token | Venceu. Todo modelo de linguagem de fronteira. |
Por que o só-decoder venceu uma disputa que parece que deveria perder? Afinal, ele vê menos contexto por token. Três razões e nenhuma é sobre elegância:
- Todo token é sinal de treino. Com a máscara causal, uma passagem direta sobre uma sequência de comprimento \(n\) produz \(n\) previsões. O BERT mascara 15% dos tokens e aprende com esses, extraindo cerca de um sexto do sinal por unidade de compute.
- Um objetivo cobre toda tarefa. "Preveja o próximo token" engloba classificação, extração, tradução e diálogo assim que o modelo fica bom o bastante para ser instruído — sem cabeça específica por tarefa, sem fine-tune separado.
- O cache de KV só funciona causalmente. Um modelo bidirecional precisa recodificar tudo quando a entrada muda; um causal apenas acrescenta. Essa é a diferença entre um chat interativo e um job em lote.
O descompasso pré-treino/inferência que matou o encoder
O BERT é treinado com entradas que contêm [MASK], um token que nunca aparece na inferência. O objetivo inteiro é um andaime que precisa ser descartado. A previsão do próximo token não tem esse buraco: o que o modelo faz no treino é exatamente o que ele faz em produção e isso é boa parte do motivo pelo qual ela escalou melhor.
Verifique você mesmo — por que não usar o BERT para escrever um texto?
Porque ele foi treinado para preencher lacunas olhando para os dois lados. Para gerar, é preciso escrever da esquerda para a direita sem o futuro, que é exatamente a situação que ele nunca viu. E mesmo que funcionasse, cada palavra nova mudaria a representação de todas as anteriores (a atenção é bidirecional), então não haveria cache de KV: cada passo recodificaria a frase inteira.
Escala: três perguntas diferentes, três respostas diferentes
A propriedade real do Transformer não é nenhuma escolha isolada de projeto; é que a perda cai de forma previsível conforme se acrescenta parâmetros, dados e compute, por muitas ordens de grandeza, sem sinal de parede3. Essa confiabilidade é o que justificou gastar um bilhão de dólares num treino.
Mas "escale" esconde uma pergunta — escalar o quê? O orçamento é o compute \(C\), medido em FLOPs, e ele se divide entre o tamanho do modelo \(N\) (parâmetros) e a quantidade de dados \(D\) (tokens de treino):
O 6 vem de contar: cada parâmetro faz cerca de 2 operações por token na ida (uma multiplicação e uma soma) e 4 na volta. Com \(C\) fixo, dobrar o modelo obriga a cortar os dados pela metade. A pergunta "escalar o quê?" é a pergunta de como repartir \(C\) entre \(N\) e \(D\) — e a resposta mudou duas vezes.
2020, Kaplan et al.3: para um orçamento fixo de compute, faça o modelo maior. O GPT-3 seguiu o conselho: 175B de parâmetros em 300B de tokens, menos de 2 tokens por parâmetro.
2022, Chinchilla4: aquilo estava errado e o erro estava no cronograma de taxa de aprendizado dos experimentos originais. Refeito direito, o ótimo é escalar parâmetros e dados juntos — cerca de 20 tokens por parâmetro. O Chinchilla, com 70B de parâmetros e 1,4T de tokens, bateu o Gopher de 280B treinado com o mesmo compute. Da noite para o dia, os modelos de todo mundo encolheram e seus conjuntos de dados cresceram.
O painel não usa os números impressos no artigo
O Chinchilla chega à conclusão por três caminhos e o terceiro — o ajuste paramétrico que o painel desenha — foi publicado com coeficientes que implicam cerca de 70 tokens por parâmetro, não 20. Eles discordam das outras duas abordagens do próprio artigo e do modelo que ele de fato treinou. Uma replicação de 2024 atribuiu isso a um otimizador mal convergido e refez o ajuste10; o painel usa os coeficientes reajustados, que voltam para a casa das dezenas. Ainda assim, espere alguma deriva da razão com o orçamento, porque os dois expoentes não são iguais — "20 tokens por parâmetro" é regra de bolso, não constante da natureza.
Hoje: ninguém treina no ótimo do Chinchilla tampouco e o painel mostra por quê. Marque precificar a inferência também. O Chinchilla minimiza o custo de um treino; um modelo em produção é pago por cada token que ele servir e o custo de servir escala com \(N\), não com \(D\). Então a jogada certa é sobretreinar um modelo menor — o Llama-3-8B viu 15T de tokens, cerca de 1900 por parâmetro, quase 100× além do Chinchilla. É ineficiente em compute de treino e muito mais barato de possuir.
Ponha os três na mesma régua com \(C \approx 6ND\):
| Modelo | \(N\) | \(D\) | tokens por parâmetro | \(C\) (FLOPs) |
|---|---|---|---|---|
| GPT-3 (2020) | 175B | 0,3T | 1,7 | \(3{,}2 \times 10^{23}\) |
| Chinchilla (2022) | 70B | 1,4T | 20 | \(5{,}9 \times 10^{23}\) |
| Llama 3 8B (2024) | 8B | 15T | ~1900 | \(7{,}2 \times 10^{23}\) |
O Chinchilla e o Llama 3 8B custaram quase o mesmo para treinar. O segundo é 9× menor, então cada token que ele gera em produção custa cerca de 9× menos — e é isso que se paga bilhões de vezes.
Duas outras correções pertencem aqui:
- Os dados são finitos. Texto de alta qualidade é um recurso limitado e repeti-lo tem retornos fortemente decrescentes depois de algumas épocas9. Isso é uma restrição real no eixo \(D\) e boa parte do motivo pelo qual dados sintéticos e curadoria são hoje áreas sérias de pesquisa.
- O compute de treino não é mais o único eixo. Um modelo também pode receber mais compute na inferência — amostrando várias cadeias de raciocínio, buscando, verificando. Essa curva também escala e é o assunto do capítulo 15.
Um bloco moderno mínimo
import torch, torch.nn as nn, torch.nn.functional as F
class Block(nn.Module):
"""Um bloco de decoder, convenções de 2026: pré-norm, RMSNorm, GQA, SwiGLU, sem vieses."""
def __init__(self, d, n_heads, n_kv_heads):
super().__init__()
self.h, self.kv, self.dh = n_heads, n_kv_heads, d // n_heads
self.n1, self.n2 = nn.RMSNorm(d), nn.RMSNorm(d)
self.wq = nn.Linear(d, d, bias=False)
self.wk = nn.Linear(d, n_kv_heads * self.dh, bias=False) # GQA: menos heads de K/V
self.wv = nn.Linear(d, n_kv_heads * self.dh, bias=False)
self.wo = nn.Linear(d, d, bias=False)
h = round(8 * d / 3 / 256) * 256 # SwiGLU mantém os parâmetros
self.w1, self.w3 = nn.Linear(d, h, bias=False), nn.Linear(d, h, bias=False)
self.w2 = nn.Linear(h, d, bias=False)
def attn(self, x, freqs):
B, T, _ = x.shape
q = self.wq(x).view(B, T, self.h, self.dh).transpose(1, 2)
k = self.wk(x).view(B, T, self.kv, self.dh).transpose(1, 2)
v = self.wv(x).view(B, T, self.kv, self.dh).transpose(1, 2)
q, k = apply_rope(q, freqs), apply_rope(k, freqs) # a posição mora aqui, não na entrada
o = F.scaled_dot_product_attention( # FlashAttention quando disponível
q, k, v, is_causal=True, enable_gqa=True)
return self.wo(o.transpose(1, 2).reshape(B, T, -1))
def forward(self, x, freqs):
x = x + self.attn(self.n1(x), freqs) # pré-norm: o fluxo fica limpo
h = self.n2(x)
return x + self.w2(F.silu(self.w1(h)) * self.w3(h)) # SwiGLU
Compare com o diagrama de 2017 e o diff é exatamente os seis controles do painel acima — confira linha a linha contra a tabela do início do capítulo. O mecanismo não mudou; a engenharia em volta dele, sim. A função apply_rope é a rotação do capítulo anterior e fica de fora por brevidade.
Pontos principais
- Um modelo de linguagem é embedding → \(L\) blocos iguais → projeção para o vocabulário. Dentro da pilha a forma é sempre \(n \times d\); cada posição prevê o token seguinte.
- Um bloco é atenção para conversar (a única troca de informação entre posições), MLP para pensar (cada token sozinho) e residuais que somam correções em vez de substituir o vetor.
- O pré-LN substituiu o pós-LN e essa é a mudança mais consequente desde 2017 — restaura o caminho identidade (\(x_L = x_0 + \sum f_\ell\)) e elimina a necessidade de warmup.
- Pense em termos de um fluxo residual do qual cada bloco lê e ao qual soma, não de um pipeline que transforma.
- RMSNorm, SwiGLU, RoPE, GQA, sem vieses — cinco padrões que você deve assumir salvo aviso em contrário. Entre dois terços e três quartos dos parâmetros de um bloco estão no MLP.
- O só-decoder venceu porque todo token é sinal de treino, um objetivo cobre toda tarefa e só a atenção causal suporta um cache de KV. Encoders sobrevivem como modelos de embedding e reranking.
- Leis de escala são o produto real do Transformer. Com \(C \approx 6ND\), Kaplan disse "vá maior", o Chinchilla corrigiu para "20 tokens por parâmetro" e a prática passou dos dois: sobretreine um modelo menor, porque a inferência é paga para sempre.
- Dados são finitos e compute na inferência é um segundo eixo de escala. Foi para lá que a fronteira se moveu.
-
Vaswani, A., et al. (2017). Attention Is All You Need — NeurIPS. ↩
-
Devlin, J., Chang, M.-W., Lee, K., & Toutanova, K. (2019). BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding — NAACL. ↩
-
Kaplan, J., et al. (2020). Scaling Laws for Neural Language Models. As leis de potência — e a conclusão pesada em parâmetros que o Chinchilla derrubou. ↩↩
-
Hoffmann, J., et al. (2022). Training Compute-Optimal Large Language Models — NeurIPS. Chinchilla; a perda paramétrica ajustada no painel acima vem daqui. ↩
-
Xiong, R., et al. (2020). On Layer Normalization in the Transformer Architecture — ICML. Por que o pós-LN precisa de warmup e o pré-LN não. ↩
-
Zhang, B., & Sennrich, R. (2019). Root Mean Square Layer Normalization — NeurIPS. ↩
-
Shazeer, N. (2020). GLU Variants Improve Transformer. Inclui a observação do próprio artigo de que ele não oferece explicação para o funcionamento. ↩
-
Warner, B., et al. (2024). Smarter, Better, Faster, Longer: A Modern Bidirectional Encoder for Fast, Memory Efficient, and Long Context Finetuning and Inference. ModernBERT; o encoder reconstruído com tudo que se aprendeu desde 2018. ↩
-
Muennighoff, N., et al. (2023). Scaling Data-Constrained Language Models — NeurIPS. O que repetir seus dados de fato compra e onde para de comprar qualquer coisa. ↩
-
Besiroglu, T., Erdil, E., Barnett, M., & You, J. (2024). Chinchilla Scaling: A replication attempt. Reconstrói os dados por trás da terceira abordagem de Hoffmann et al., mostra que os coeficientes publicados não os ajustam, e refaz o ajuste. ↩