Ir para o conteúdo

Normalização e Ativações Modernas

O Capítulo 1 fixou o esqueleto de um bloco decoder moderno: um residual stream pontuado por subcamadas de attention e subcamadas feed-forward, com normalização posicionada antes de cada subcamada em vez de depois. O Capítulo 2 trocou os embeddings de posição absoluta aprendidos por RoPE dentro do caminho de attention. Este capítulo troca mais dois componentes que o Transformer original de 2017 especificou de uma forma e que cada modelo Llama, Qwen, Gemma e DeepSeek agora especifica de forma diferente:

  • a própria normalização, que não é mais LayerNorm e sim RMSNorm;
  • a subcamada feed-forward, que não é mais uma pilha de duas matrizes com ReLU ou GELU, mas uma estrutura gated de três matrizes chamada SwiGLU.

Nenhuma das mudanças é teoricamente profunda. Ambas são vitórias empíricas, ambas foram propostas anos antes de serem amplamente adotadas, e ambas se tornaram padrão não porque a matemática as forçou, mas porque o ecossistema de pesos abertos coordenou-se em torno da mesma receita em escala.

1. Por que renormalizar afinal

🔗 Conexão

O posicionamento pre-norm e o residual stream foram introduzidos no Capítulo 1; os rotary position embeddings (RoPE), a troca do lado da attention referenciada acima, foram derivados no Capítulo 2. Ambos são pré-requisitos para o bloco consolidado de §9.

Mantemos a convenção pre-norm do Capítulo 1: cada subcamada lê-se como \(y = x + \mathrm{Sublayer}(\mathrm{Norm}(x))\), com Norm aplicada ao residual stream imediatamente antes da attention ou antes da FFN, e o residual somado sem normalização [src_010, src_002]. O trabalho da normalização é manter a magnitude das ativações e dos gradientes limitada ao longo da profundidade para que o treinamento não divirja — não remodelar a distribuição em algo estatisticamente impecável [src_018]. A formulação original do "internal covariate shift" foi contestada por trabalhos posteriores, e a leitura operacional que sobrevive é mais simples: a normalização controla a magnitude das ativações, e esse controle é o que estabiliza a otimização [src_018].

Este capítulo trata do quê dentro da Norm e dentro da FFN. O posicionamento pre-norm está fixado.

2. De LayerNorm a RMSNorm

🎯 Intuição

LayerNorm subtrai a média ao longo do eixo de \(D\) features, divide pelo desvio-padrão, e em seguida reaplica um ganho e um viés por feature. Geometricamente, a subtração projeta o vetor de ativação sobre o hiperplano perpendicular à direção dos uns em feature space — ela remove qualquer componente do vetor que aponte ao longo de \((1, 1, \dots, 1)\). A próxima seção pergunta se o residual stream já vive nesse hiperplano, caso em que a subtração não está fazendo trabalho algum.

LayerNorm, tal como introduzida para Transformers, normaliza as ativações de um token \(a \in \mathbb{R}^D\) ao longo da dimensão de feature subtraindo a média e dividindo pelo desvio-padrão, e em seguida aplica um ganho aprendido por feature \(g\) e um viés \(b\):

\[ \mathrm{LayerNorm}(a)_i \;=\; \frac{a_i - \mu}{\sigma} \cdot g_i + b_i, \qquad \mu = \frac{1}{D}\sum_{j=1}^{D} a_j, \qquad \sigma = \sqrt{\frac{1}{D}\sum_{j=1}^{D} (a_j - \mu)^2 + \varepsilon}. \]

🤔 Pause e pense

Antes de seguir, preveja — se você deletar o termo de média (\(a_i - \mu \to a_i\)) do LayerNorm mas mantiver o reescalamento pelo desvio-padrão, qual das duas invariâncias (re-centragem, re-escala) sobrevive, e por quê? (Não olhe adiante — escreva a resposta ou diga em voz alta.)

Duas invariâncias decorrem dessa definição. Invariância de re-centragem: deslocar a entrada ou os pesos a montante por uma constante deixa a saída do LayerNorm inalterada. Invariância de re-escala: multiplicar a entrada ou os pesos a montante por uma constante positiva também deixa a saída inalterada [src_018].

Zhang e Sennrich perguntaram qual dessas duas invariâncias está fazendo o trabalho real de estabilização. A hipótese deles era de que a re-centragem é dispensável: o residual stream em um Transformer já absorve deslocamentos aditivos porque cada subcamada soma de volta a ele, então subtrair a média novamente em cada Norm é em grande parte redundante [src_018]. Eles propuseram descartar o termo de média e o viés aditivo e manter apenas o segundo momento:

\[ \mathrm{RMSNorm}(x)_i \;=\; \frac{x_i}{\mathrm{RMS}(x)} \cdot \gamma_i, \qquad \mathrm{RMS}(x) = \sqrt{\frac{1}{D}\sum_{j=1}^{D} x_j^{2} + \varepsilon}, \]

onde \(\gamma \in \mathbb{R}^{D}\) é o ganho aprendido por feature e \(\varepsilon\) é um pequeno estabilizador numérico (tipicamente \(10^{-5}\) ou \(10^{-6}\)) [src_018]. Não há viés aprendido nem subtração de média. Quando a média da entrada é zero, RMSNorm e LayerNorm coincidem exatamente; quando não é, RMSNorm sacrifica a invariância de re-centragem e mantém apenas a invariância de re-escala [src_018].

A economia é visível no nível da própria fórmula. LayerNorm precisa de duas passadas sobre a dimensão de feature (uma para \(\mu\), outra para \(\sigma\)) mais uma subtração, enquanto RMSNorm precisa de uma passada (a soma dos quadrados) e nenhuma subtração; LayerNorm tem \(2D\) parâmetros aprendidos por layer (\(g\) e \(b\)), enquanto RMSNorm tem \(D\) (somente \(\gamma\)) [src_018].

3. O argumento empírico para descartar a média

O argumento publicado a favor do RMSNorm é empírico, não teórico. A Tabela 1 de Zhang e Sennrich relata reduções de tempo de execução de 7%–64% em tradução automática (RNNSearch, Transformer), classificação de imagem, recuperação imagem-legenda e question answering, com qualidade downstream igualando LayerNorm dentro do ruído entre execuções [src_018]. Eles ainda mostram que estimar o RMS a partir de um subconjunto de 6,25% das features — um RMSNorm parcial — permanece competitivo [src_018]. As notas de aula do CS336 de Stanford tratam RMSNorm como a escolha moderna de fato, citando o mesmo resultado de velocidade-sem-perda-de-qualidade como a razão pela qual a família Llama o adotou [src_004].

Duas coisas merecem honestidade. Primeiro, o speedup é wall-clock, não teórico: o ganho vem de menos reduções e menos parâmetros aprendidos, não de uma garantia matemática mais forte. Segundo, a alegação de paridade de qualidade é comparável, não melhor. Nada no artigo original do RMSNorm argumenta que descartar a média melhora a modelagem; argumenta apenas que não se perde nada ao fazê-lo [src_018]. Esse é o enquadramento que os praticantes modernos herdam.

💡 Resultado-chave

RMSNorm iguala LayerNorm em qualidade e roda mais rápido — mas o speedup é wall-clock vindo de menos reduções, não de uma garantia matemática mais forte.

🔄 Recapitulação

  • Escreva a fórmula do RMSNorm a partir da do LayerNorm: quais termos desaparecem, e qual é a contagem de parâmetros por layer comparada com os \(2D\) do LayerNorm?
  • Das duas invariâncias do LayerNorm (re-centragem, re-escala), qual sobrevive em RMSNorm, e por que o residual stream torna esse sobrevivente suficiente?
  • Preveja: se você também descartasse o ganho aprendido \(\gamma\) do RMSNorm, ao que o operador se reduziria, e qual papel de estabilização seria perdido?

4. De ReLU e GELU a ativações gated

A segunda troca diz respeito à rede feed-forward position-wise (a FFN é aplicada independentemente a cada posição de token; sem interação entre posições). O Transformer original de 2017 especificou uma FFN da forma

\[ \mathrm{FFN}_{\text{ReLU}}(x) \;=\; \mathrm{ReLU}(x W_{1}) \, W_{2}, \]

com a convenção \(W_1 \in \mathbb{R}^{D \times d_{ff}}\), \(W_2 \in \mathbb{R}^{d_{ff} \times D}\) e \(d_{ff} = 4D\) para o modelo base [src_019]. Trabalhos posteriores substituíram ReLU por alternativas suaves não monotônicas: GELU, definida por \(\mathrm{GELU}(x) = x \cdot \Phi(x)\) onde \(\Phi\) é a CDF normal padrão, e Swish (também chamada SiLU), definida por \(\mathrm{Swish}_{\beta}(x) = x \cdot \sigma(\beta x)\) com \(\sigma\) sendo a sigmoide logística [src_019]. Ao longo deste capítulo e ao longo da maioria dos artigos modernos, \(\mathrm{Swish}\) significa \(\mathrm{Swish}_{1}\) — a mesma função que \(\mathrm{SiLU}\). Tanto Swish quanto GELU são substitutos diretos pontuais de ReLU, e ambas deixam a estrutura de duas matrizes da FFN intacta.

⚠️ Armadilha

Três nomes referem-se à mesma função: \(\mathrm{Swish}_{1}\), \(\mathrm{SiLU}\) e o \(\mathrm{F.silu}\) do PyTorch. O "Swi" em SwiGLU é exatamente esse Swish com \(\beta = 1\), não um gate diferente. Encontrar os três nomes em artigos, em código e na listagem de §8 sem sinalizar a equivalência é uma confusão comum.

4.1 Gating: de um caminho para dois

🎯 Intuição

Uma ativação pontual (ReLU, GELU, Swish) é um caminho: projetar, depois não-linearidade, depois projetar de volta. Uma ativação gated são dois caminhos multiplicados: projetar uma vez em um valor, projetar novamente em um gate, passar o gate por uma não-linearidade escalar, e em seguida multiplicar os dois elemento a elemento. O gate é dependente dos dados — ele permite que cada feature decida, por token, quanto do valor manter. Ativações pontuais não conseguem fazer isso; a decisão delas é fixa pela função de ativação.

A contribuição de Shazeer em 2020 foi ortogonal: em vez de substituir ReLU por uma não-linearidade pontual diferente, substituir o primeiro estágio linear-mais-não-linearidade por uma estrutura gated no espírito das Gated Linear Units de Dauphin et al. A camada GLU geral é

\[ \mathrm{GLU}(x, W, V, b, c) \;=\; \sigma(x W + b) \,\odot\, (x V + c), \]

isto é, o produto elemento a elemento de duas projeções lineares de \(x\), em que uma das projeções foi passada por uma sigmoide [src_019].

🤔 Pause e pense

Dada a forma geral de GLU \(\sigma(x W + b) \odot (x V + c)\), preveja o que muda quando a função de gate \(\sigma\) é substituída por ReLU, GELU ou Swish. Qual substituição preserva a intuição de gating (peso multiplicativo dependente dos dados) ao mesmo tempo em que remove a sigmoide saturante? (Não olhe adiante.)

As variantes vêm de substituir o gate sigmoide por uma não-linearidade escalar diferente:

\[ \begin{aligned} \mathrm{ReGLU}(x, W, V, b, c) &= \mathrm{ReLU}(x W + b) \odot (x V + c), \\ \mathrm{GeGLU}(x, W, V, b, c) &= \mathrm{GELU}(x W + b) \odot (x V + c), \\ \mathrm{SwiGLU}(x, W, V, b, c, \beta) &= \mathrm{Swish}_{\beta}(x W + b) \odot (x V + c). \end{aligned} \]

Inserindo-as na FFN, com a configuração T5 convencional sem viés, obtém-se a família de variantes feed-forward que Shazeer testou [src_019]:

\[ \mathrm{FFN}_{\text{SwiGLU}}(x, W, V, W_{2}) \;=\; \big(\mathrm{Swish}_{1}(x W) \odot x V\big) \, W_{2}. \]

Há agora três matrizes de pesos, não duas: a projeção de gate \(W\), a projeção de valor \(V\) e a projeção de saída \(W_{2}\).

5. O que a tabela de Shazeer realmente mostra

Shazeer treinou um modelo T5-base com cada variante de FFN no objetivo de span-filling do C4 por 524k passos, e em seguida fez fine-tuning em GLUE, SuperGLUE e SQuAD. A Tabela 1 do artigo relata log-perplexidade no conjunto heldout ao final do pré-treinamento; nessa tabela, as baselines ReLU e GELU ficam em 1,677 e 1,679 respectivamente, enquanto GeGLU e SwiGLU ficam em 1,633 e 1,636 — separadas entre si por 0,003, uma diferença bem abaixo dos desvios-padrão entre sementes (random seeds) que o mesmo artigo relata para execuções mais curtas [src_019].

🎯 Intuição

Variante Log-perplexidade no heldout
ReLU 1,677
GELU 1,679
GeGLU 1,633
SwiGLU 1,636

A diferença entre o par GLU e o par pontual (~0,04) é real; a diferença dentro do par GLU (0,003) está dentro do ruído entre execuções.

A história downstream é semelhante: GeGLU, SwiGLU, ReGLU e Bilinear todos batem ReLU e GELU na maioria das tarefas de GLUE e SuperGLUE, mas a ordem entre as próprias entradas da família GLU varia de tarefa para tarefa e está em grande parte dentro do ruído [src_019].

Shazeer é explícito sobre a ausência de uma razão teórica. Ele observa que as camadas da família GLU são simples de implementar e não têm desvantagem computacional aparente, e não oferece explicação adicional, atribuindo as vitórias à "benevolência divina" [src_019]. O sentido de citar essa expressão de duas palavras não é a piada, é a honestidade metodológica: o artigo apresenta um ranking empírico e recusa-se a vesti-lo como uma derivação.

⚠️ Armadilha

Ler os resultados da família GLU como se tivessem uma base teórica confunde honestidade metodológica com ausência teórica — a ausência é o resultado, não uma lacuna a ser preenchida. Shazeer apresenta um ranking empírico; nenhum argumento analítico separado existe, e qualquer um que um leitor forneça post hoc é exatamente o tipo de vestimenta que o artigo original recusou.

6. Por que SwiGLU domina na prática

Se GeGLU e SwiGLU estão estatisticamente empatados na própria tabela de Shazeer, por que SwiGLU é a escolha universal nas famílias de pesos abertos que definiram a era 2023–2026? A resposta é propagação de convenção, não superioridade medida.

GeGLU foi a escolha dominante na onda anterior de modelos grandes, incluindo o PaLM do Google, em que a FFN é gated e usa GELU como ativação de gate [src_019]. Quando a Meta lançou o Llama, a decisão de design foi usar SwiGLU em vez de GeGLU; Llama-2 e Llama-3 mantiveram SwiGLU; o ecossistema de pesos abertos que cresceu sobre o Llama — Qwen, DeepSeek, derivativos do Mistral e a maioria das famílias decoder-only recentes descritas como estilo Llama — herdou essa escolha [src_002, src_047]. GeGLU não desapareceu: é a escolha de gating do ModernBERT (uma revivescência encoder-only de 2024 equipada com RoPE, abordada no Capítulo 7) e de vários derivativos do T5 anteriores à linhagem Llama [src_002, src_047]. Os dois coexistem porque nenhum benchmark jamais produziu uma separação clara entre eles.

A afirmação pedagogicamente honesta é, portanto: SwiGLU é padrão porque Llama é a arquitetura de referência de fato para decoders de pesos abertos, não porque alguém demonstrou que gates Swish vencem gates GELU em um teste heldout justo [src_019, src_002, src_047].

🔗 Conexão

As famílias de decoders de pesos abertos nomeadas nesta seção — Llama, Qwen, Gemma, DeepSeek, derivativos do Mistral — são abordadas no Capítulo 8, onde a alegação de coordenação ecossistêmica é examinada em detalhe.

7. Paridade de parâmetros e o multiplicador 8/3

Uma FFN SwiGLU tem três matrizes de pesos em vez de duas, então substituir ingenuamente \(\mathrm{FFN}_{\text{ReLU}}\) por \(\mathrm{FFN}_{\text{SwiGLU}}\) na mesma largura oculta infla a contagem de parâmetros da FFN em 50%. A solução de Shazeer foi reescalar a largura oculta \(d_{ff}\) para manter o total constante. Com a configuração T5 convencional \(d_{ff} = 4D\), a FFN ReLU de duas matrizes tem \(2 \cdot D \cdot d_{ff} = 8 D^{2}\) parâmetros. Dois passos estão dobrados nessa contagem: (i) a FFN ReLU de duas matrizes tem \(W_1 \in \mathbb{R}^{D \times d_{ff}}\) e \(W_2 \in \mathbb{R}^{d_{ff} \times D}\), contribuindo \(2 \cdot D \cdot d_{ff}\) parâmetros no total; (ii) substituindo a convenção T5 \(d_{ff} = 4D\) obtém-se \(2 \cdot D \cdot 4D = 8 D^{2}\). A FFN SwiGLU de três matrizes tem \(3 \cdot D \cdot d_{ff}\) parâmetros; igualando os dois:

\[ 3 \cdot D \cdot d_{ff}^{\text{SwiGLU}} \;=\; 8 D^{2} \quad\Longrightarrow\quad d_{ff}^{\text{SwiGLU}} \;=\; \frac{8}{3} D \;\approx\; 2.67\, D. \]

Esta é a origem da largura de FFN \(\tfrac{8}{3}D\) que as configurações da família Llama anunciam. O próprio artigo de Shazeer descreveu a mesma operação como multiplicar o \(d_{ff}\) original por \(\tfrac{2}{3}\): na configuração T5-base ele partiu de \(d_{ff} = 3072\) e reduziu para \(d_{ff} = 2048\) para igualar contagens de parâmetros e de computação contra a baseline de duas matrizes [src_019]. A aula sobre normalização e MLP do CS336 trata esse reescalamento como a receita canônica do SwiGLU e é a referência de livro-texto que as implementações modernas seguem [src_004]. Em código de produção, a largura oculta resultante é frequentemente arredondada para o múltiplo de 64 ou 128 mais próximo para alinhar-se aos tamanhos de tile dos tensor cores (unidades de multiplicação de matrizes da GPU que operam sobre tiles de tamanho fixo pequeno, tipicamente 16×16 ou 16×32), razão pela qual checkpoints Llama às vezes relatam uma largura oculta próxima mas não exatamente \(\tfrac{8}{3}D\).

O ponto conceitual é que, sob paridade de parâmetros, a FFN SwiGLU troca um gargalo mais largo de matriz única por um gargalo mais estreito que é gated por uma projeção extra. A alegação empírica de que essa troca compensa é o resultado da Tabela 1 de Shazeer; não há um argumento analítico separado [src_019].

💡 Resultado-chave

As duas trocas deste capítulo — RMSNorm e SwiGLU — são vitórias empíricas sem justificativas analíticas separadas; o bloco moderno é em torno do que o ecossistema de pesos abertos se coordenou, não o que a teoria forçou.

8. Implementação de referência em PyTorch

Os dois ingredientes deste capítulo, RMSNorm e SwiGLU, são curtos o suficiente para que uma implementação fiel caiba em uma página. A listagem abaixo segue o estilo da implementação de referência from-scratch de Raschka e da implementação canônica da Aula 3 do CS336 [src_010, src_004].

import torch
import torch.nn as nn
import torch.nn.functional as F


class RMSNorm(nn.Module):
    """Root mean square layer normalization (Zhang & Sennrich, 2019).

    Drops the mean term and the additive bias of LayerNorm; keeps a learned
    per-feature gain. The fast form uses rsqrt to avoid an explicit divide.
    """

    def __init__(self, dim: int, eps: float = 1e-6):
        super().__init__()
        self.eps = eps
        self.gamma = nn.Parameter(torch.ones(dim))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # x: (B, T, D). Compute mean square over the last dim.
        ms = x.pow(2).mean(dim=-1, keepdim=True)
        x_hat = x * torch.rsqrt(ms + self.eps)
        return x_hat * self.gamma


class SwiGLU(nn.Module):
    """SwiGLU feed-forward sublayer (Shazeer, 2020).

    Three weight matrices: w_gate and w_value share the input dim D and
    project to hidden dim d_ff; w_out projects back to D. The hidden dim
    is conventionally 8/3 * D in Llama-family configs to keep parameter
    parity with a 4*D-wide ReLU/GELU FFN.
    """

    def __init__(self, dim: int, hidden_dim: int | None = None):
        super().__init__()
        if hidden_dim is None:
            hidden_dim = int(round(8 * dim / 3))
        self.w_gate = nn.Linear(dim, hidden_dim, bias=False)
        self.w_value = nn.Linear(dim, hidden_dim, bias=False)
        self.w_out = nn.Linear(hidden_dim, dim, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.w_out(F.silu(self.w_gate(x)) * self.w_value(x))

Algumas observações sobre a listagem. torch.rsqrt(ms + eps) é a forma que os kernels de attention rápidos e os kernels CUDA modernos de RMSNorm efetivamente computam: uma única instrução de raiz quadrada recíproca é mais barata que uma raiz quadrada seguida de uma divisão, e o tensor resultante é multiplicado (não dividido) em \(x\) [src_004]. F.silu é o nome do PyTorch para \(\mathrm{SiLU}\), que é a mesma função que \(\mathrm{Swish}_{1}\); o "Swi" em SwiGLU refere-se, portanto, exatamente a esse gate [src_019]. As três projeções são sem viés porque as configurações decoder-only modernas na linhagem Llama descartam parâmetros de viés da FFN inteira, espelhando a convenção sem viés do T5 que Shazeer usou [src_019, src_010].

9. O bloco moderno consolidado

Combinando as duas substituições deste capítulo com a substituição do RoPE do Capítulo 2 e a substituição do GQA que o Capítulo 4 derivará em detalhes, um único bloco decoder em um LLM moderno de pesos abertos lê-se:

input  -> RMSNorm -> Attention(Q, K with RoPE; K, V grouped per GQA) -> + residual
       -> RMSNorm -> SwiGLU FFN                                       -> + residual
output

A chamada de attention compartilha queries entre múltiplos grupos de heads de chave/valor (GQA, derivado no Capítulo 4), o rotary embedding é aplicado a Q e K apenas (não a V) conforme o Capítulo 2, e os dois RMSNorms operam sobre o residual stream antes de cada subcamada conforme a convenção pre-norm do Capítulo 1 [src_010, src_002, src_047]. O mesmo esqueleto aparece nas configurações decoder Llama, Qwen, Gemma e DeepSeek, com diferenças apenas em dimensões, contagens de heads e contagens de grupos [src_002, src_047].

O bloco decoder-only moderno como implantado na família Llama / Qwen / Gemma / DeepSeek.

A figura torna a topologia residual explícita: cada subcamada recebe o residual stream como entrada, normaliza-o através de RMSNorm antes de transformá-lo, e soma o resultado transformado de volta ao stream. RoPE é interno aos caminhos \(Q\) e \(K\) da subcamada de attention. As três projeções do SwiGLU são visíveis como as três matrizes dentro da subcamada FFN.

Isto conclui o tour de modernização dos componentes dentro de um bloco. O Capítulo 4 vira a câmera e pergunta o que muda quando esses blocos são empilhados profundamente o suficiente para que a própria attention se torne o custo dominante em tempo de inferência — momento em que GQA, FlashAttention e a engenharia do KV-cache se tornam inevitáveis.

🔗 Conexão

Três referências futuras nesta seção apontam todas para o mesmo capítulo: GQA (a attention de grupos de chave/valor mostrada no bloco consolidado), FlashAttention (o kernel exato de attention consciente de IO) e KV-cache (o cache de decodificação autoregressiva). Os três são derivados no Capítulo 4.

🔄 Recapitulação

  • Complete: RMSNorm difere de LayerNorm por descartar ___ e ___, retendo apenas ___ por feature; isso o deixa com a invariância de ___ e descarta a invariância de ___.
  • Compare: nomeie as três matrizes de pesos de uma FFN SwiGLU e seus papéis, e contraste com a FFN ReLU de duas matrizes.
  • Derive: partindo de \(2 \cdot D \cdot d_{ff} = 8 D^{2}\) e da restrição de paridade de parâmetros com três matrizes, recupere o multiplicador \(\tfrac{8}{3}D\) sem reler §7.
  • Preveja: de quais dois capítulos pré-requisitos depende o bloco consolidado de §9, e qual capítulo posterior introduz GQA, FlashAttention e KV-cache?

Referências

  • [src_002] Xiao, T. and Zhu, J. (2025). Foundations of Large Language Models. arXiv:2501.09223v2. URL: https://arxiv.org/pdf/2501.09223
  • [src_004] Hashimoto, T. and Liang, P. (2025). Stanford CS336: Language Modeling from Scratch (Spring 2025). URL: https://stanford-cs336.github.io/spring2025/
  • [src_010] Raschka, S. (2024). Build a Large Language Model (From Scratch). Manning Publications. URL: https://github.com/rasbt/LLMs-from-scratch
  • [src_018] Zhang, B. and Sennrich, R. (2019). Root Mean Square Layer Normalization. arXiv:1910.07467. URL: https://arxiv.org/pdf/1910.07467
  • [src_019] Shazeer, N. (2020). GLU Variants Improve Transformer. arXiv:2002.05202. URL: https://arxiv.org/pdf/2002.05202
  • [src_047] Grigorov, D. (2026). Building Large Language Models from Scratch. Apress. URL: https://doi.org/10.1007/979-8-8688-2297-1