Ir para o conteúdo

De Ponta a Ponta

Os dois capítulos abriram o caminho uma página por vez. Esta página fecha o caminho em um lugar só, no desenho do Transformer Explainer e com os números do modelo de brinquedo. O caminho é uma sequência numerada: começa antes da atenção, com a tabela de embedding e a matriz feita a partir dela, passa pelas cabeças (escolha quantas) e termina no próximo token. Clique num passo: aquela parte do diagrama abre ali mesmo e o quadro ao lado explica, com as contas. No fim da página, o mesmo caminho é feito à mão.

← Geração · próximo: O que mudou desde 2017 →

Onde estão os pesos

peça forma brinquedo GPT-2 small (12 blocos)
tabela de embeddings \(E\) \(V \times d\) 12 × 4 = 48 38,6 milhões
tabela de posições \(P\) posições × \(d\) 8 × 4 = 32 786 mil
\(W_Q\), \(W_K\), \(W_V\) \(3 \times (d \times d)\) 48 21,2 milhões
\(W_O\) \(d \times d\) 16 7,1 milhões
MLP: \(W_1\), \(W_2\) \(d \times\) largura e largura \(\times\, d\) 32 + 32 = 64 56,6 milhões
saída \(W_U\) \(d \times V\) 48 amarrada a \(E\)
total 256 ≈ 124 milhões

Nas contas do GPT-2 ficam de fora os vieses e as LayerNorms, que são uma parcela pequena. No brinquedo as LayerNorms não têm peso aprendido e os pesos foram escolhidos à mão, para que cada conta possa ser conferida. A tabela completa compara os dois modelos.

Para conferir

Verifique você mesmo — se todas as chaves forem idênticas, qual é a saída?

Os scores ficam iguais, o softmax vira uniforme e a saída é a média simples dos valores, a mesma para qualquer query.

Verifique você mesmo — o que sobra se você tirar a atenção de todos os blocos?

Cada token passa pela pilha sem saber quem são os vizinhos. Depois de "no", o modelo diria sempre a mesma coisa, fosse a frase sobre um gato ou sobre um banco.

Verifique você mesmo — a temperatura muda qual token é o mais provável?

Não. Dividir os logits pelo mesmo \(T\) positivo mantém a ordem. Muda só quanta massa os outros tokens recebem.

Verifique você mesmo — some 10 a todos os logits da última linha. O que muda?

Nada: o \(e^{10}\) se cancela entre o numerador e a soma. Só as diferenças entre logits importam.

Exemplo numérico, passo a passo

O caminho inteiro à mão, de "o gato subiu no" até o próximo token, com \(h = 2\) cabeças, a máscara ligada e \(T = 1\). São os números do simulador com a frase "o gato subiu no". Eles aparecem arredondados em 3 casas, mas cada conta carrega todas as casas, então o último dígito pode diferir em um.

Passo 1. Do texto aos ids

O tokenizador corta o texto em pedaços de um vocabulário fixado antes e cada pedaço vira o número da sua linha:

token o ␣gato ␣sub iu ␣no
id 0 2 4 5 1

Passo 2. A tabela \(E\): como ela é calculada

\(E\) existe antes de qualquer frase e antes da atenção. Ela tem uma linha por token do vocabulário e uma coluna por dimensão: \(V \times d = 12 \times 4\). Os números dela vêm de três contas, nesta ordem.

a. Criação: números sorteados. Um modelo real começa cada número de um sorteio. O GPT-2 sorteia de \(\mathcal{N}(0;\ 0{,}02^2)\), com \(V \times d = 50257 \times 768\). Uma linha sorteada assim, com \(d = 4\):

\[[0{,}003;\ -0{,}003;\ 0{,}013;\ 0{,}002]\]

Essa linha ainda não significa nada: neste ponto o vetor de ␣telhado é tão bom quanto o de qualquer outro token.

b. Treino: cada número anda contra a sua derivada. Com a perda \(L\) de uma previsão e uma taxa de aprendizado \(\eta\), a atualização é

\[E_{i,c} \leftarrow E_{i,c} - \eta\, \frac{\partial L}{\partial E_{i,c}}\]

O brinquedo pula o sorteio e os muitos passos: a tabela dele foi escolhida à mão, para que cada conta possa ser conferida. É esta, como se já estivesse treinada, com as linhas desta frase em negrito:

id token \(c_1\) \(c_2\) \(c_3\) \(c_4\)
0 o −0,5 −0,5 0 1
1 ␣no −0,5 0 −0,5 1
2 ␣gato 1 −0,5 0 −0,5
3 ␣cão 1 −0,5 −0,5 0
4 ␣sub −0,5 1 0 −0,5
5 iu 0 0,5 −0,5 0
6 ␣telhado 0 −0,5 1 −0,5
7 ␣muro 1 0 −1 0
8 ␣sofá 0,5 −0,5 −0,5 0,5
9 ␣chão 0,5 0 −0,5 0
10 . 0 0 1 −1
11 <eot> 0,5 −0,5 0,5 −0,5

Um passo de treino sobre ela, para ver a conta. A posição "o gato subiu no" deveria prever ␣telhado, então a perda é

\[L = -\ln p(\text{␣telhado}) = -\ln 0{,}395 = 0{,}928\]

A derivada de \(L\) em cada linha da frase é:

id token \(\partial L / \partial E_{i}\)
0 o [−0,001; 0,000; 0,001; 0,000]
2 ␣gato [0,000; 0,000; 0,026; −0,026]
4 ␣sub [−0,001; 0,000; 0,001; 0,000]
5 iu [0,002; 0,002; 0,000; −0,004]
1 ␣no [−0,011; 0,011; 0,027; −0,028]

As outras 7 linhas têm derivada exatamente 0: um token que não está na frase não se move. Com \(\eta = 0{,}1\), a linha de ␣no (id 1) vira:

\[\begin{aligned} -0{,}5 - 0{,}1 \cdot (-0{,}011) &= -0{,}499 \\ 0{,}0 - 0{,}1 \cdot (0{,}011) &= -0{,}001 \\ -0{,}5 - 0{,}1 \cdot (0{,}027) &= -0{,}503 \\ 1{,}0 - 0{,}1 \cdot (-0{,}028) &= 1{,}003 \end{aligned}\]

Depois desse único passo, com todas as linhas atualizadas, \(p(\text{␣telhado})\) vai de \(0{,}395\) para \(0{,}396\) e a perda vai de \(0{,}928\) para \(0{,}927\). Repetir isso em muitas frases é o que preenche a tabela. O resto deste exemplo usa a tabela acima, sem o passo.

c. Leitura: \(E[\text{id}]\). Com a tabela pronta, ler uma linha é um produto por um vetor de zeros com um 1 no id. Para ␣no, id 1:

\[E[1] = [0,\ 1,\ 0,\ 0,\ 0,\ 0,\ 0,\ 0,\ 0,\ 0,\ 0,\ 0]\ E = [-0{,}5;\ 0{,}0;\ -0{,}5;\ 1{,}0]\]

As cinco leituras, uma por token, formam a matriz \(E[\text{ids}]\):

token id \(E[\text{id}]\)
o 0 [−0,5; −0,5; 0,0; 1,0]
␣gato 2 [1,0; −0,5; 0,0; −0,5]
␣sub 4 [−0,5; 1,0; 0,0; −0,5]
iu 5 [0,0; 0,5; −0,5; 0,0]
␣no 1 [−0,5; 0,0; −0,5; 1,0]

Passo 3. A tabela de posições \(P\) e a matriz \(X\)

\(E\) não diz onde o token está. Cada posição tem uma linha de \(P\), também uma tabela de pesos. A soma das duas linhas é a linha de \(X\), a única coisa que o bloco recebe:

token \(E[\text{id}]\) \(P_t\) \(x_t = E[\text{id}] + P_t\)
o [−0,5; −0,5; 0,0; 1,0] [0,0; 0,0; 0,0; 0,0] [−0,5; −0,5; 0,0; 1,0]
␣gato [1,0; −0,5; 0,0; −0,5] [0,5; 0,0; −0,5; 0,0] [1,5; −0,5; −0,5; −0,5]
␣sub [−0,5; 1,0; 0,0; −0,5] [0,0; 0,5; 0,0; −0,5] [−0,5; 1,5; 0,0; −1,0]
iu [0,0; 0,5; −0,5; 0,0] [−0,5; 0,0; 0,5; 0,0] [−0,5; 0,5; 0,0; 0,0]
␣no [−0,5; 0,0; −0,5; 1,0] [0,0; −0,5; 0,0; 0,5] [−0,5; −0,5; −0,5; 1,5]

Passo 4. LayerNorm

Daqui em diante acompanhamos a última linha, a que prevê o próximo token, ␣no. Cada linha é normalizada sozinha:

\[\mu = 0{,}000,\quad \sigma = 0{,}866,\quad u_5 = \frac{x_5 - \mu}{\sigma} = [-0{,}577;\ -0{,}577;\ -0{,}577;\ 1{,}732]\]

As cinco linhas de \(u\):

token \(u_1\) \(u_2\) \(u_3\) \(u_4\)
o −0,816 −0,816 0,000 1,633
␣gato 1,732 −0,577 −0,577 −0,577
␣sub −0,535 1,604 0,000 −1,069
iu −1,414 1,414 0,000 0,000
␣no −0,577 −0,577 −0,577 1,732

Passo 5. Query, Key, Value

Três matrizes de pesos multiplicam \(u\). No brinquedo elas só escolhem colunas: \(Q = [u_4, u_2, u_4, u_1]\), \(K = [u_2, u_4, u_1, u_4]\) e \(V = [u_1, u_2, u_3, u_4]\). A cabeça 1 lê as colunas 1 e 2 e a cabeça 2 lê as colunas 3 e 4.

token \(Q\) \(K\) \(V\)
o [1,633; −0,816; 1,633; −0,816] [−0,816; 1,633; −0,816; 1,633] [−0,816; −0,816; 0,000; 1,633]
␣gato [−0,577; −0,577; −0,577; 1,732] [−0,577; −0,577; 1,732; −0,577] [1,732; −0,577; −0,577; −0,577]
␣sub [−1,069; 1,604; −1,069; −0,535] [1,604; −1,069; −0,535; −1,069] [−0,535; 1,604; 0,000; −1,069]
iu [0,000; 1,414; 0,000; −1,414] [1,414; 0,000; −1,414; 0,000] [−1,414; 1,414; 0,000; 0,000]
␣no [1,732; −0,577; 1,732; −0,577] [−0,577; 1,732; −0,577; 1,732] [−0,577; −0,577; −0,577; 1,732]

Passo 6. A atenção de cada cabeça

A linha de ␣no contra cada coluna, \(s_{5,j} = q_5 \cdot k_j / \sqrt{2}\), depois \(a_{5,j} = e^{s_{5,j}} / \sum e^{s}\). É a última linha, então a máscara não apaga nada aqui.

Cabeça 1, com \(q_5 = [1{,}732;\ -0{,}577]\):

\(j\) \(k_j\) \(q_5 \cdot k_j\) \(s_{5,j}\) \(e^{s}\) \(a_{5,j}\)
o [−0,816; 1,633] −2,357 −1,667 0,189 0,011
␣gato [−0,577; −0,577] −0,667 −0,471 0,624 0,035
␣sub [1,604; −1,069] 3,395 2,400 11,027 0,622
iu [1,414; 0,000] 2,449 1,732 5,652 0,319
␣no [−0,577; 1,732] −2,000 −1,414 0,243 0,014

Cabeça 2, com \(q_5 = [1{,}732;\ -0{,}577]\):

\(j\) \(k_j\) \(q_5 \cdot k_j\) \(s_{5,j}\) \(e^{s}\) \(a_{5,j}\)
o [−0,816; 1,633] −2,357 −1,667 0,189 0,016
␣gato [1,732; −0,577] 3,333 2,357 10,559 0,882
␣sub [−0,535; −1,069] −0,309 −0,218 0,804 0,067
iu [−1,414; 0,000] −2,449 −1,732 0,177 0,015
␣no [−0,577; 1,732] −2,000 −1,414 0,243 0,020

A cabeça 1 olha para o verbo, que o tokenizador cortou em dois (␣sub + iu = 0,94). A cabeça 2 olha para o sujeito, ␣gato com 0,88. Na linha de ␣sub, a terceira, a máscara escreve \(-\infty\) nas posições 4 e 5 e os pesos delas são 0: [0,930; 0,063; 0,007; 0,000; 0,000].

Passo 7. Mistura os values e junta as cabeças

Cada cabeça tira a média dos values com os seus pesos, os dois resultados ficam lado a lado e \(W_O = I\) os deixa como estão:

\[o_5^{(1)} = \sum_j a_{5,j}\, v_j = [-0{,}739;\ 1{,}411],\quad o_5^{(2)} = [-0{,}521;\ -0{,}520],\quad a_5 = [-0{,}739;\ 1{,}411;\ -0{,}521;\ -0{,}520]\]

Passo 8. Primeira soma, LayerNorm e MLP

\[\begin{aligned} h_5 &= x_5 + a_5 = [-1{,}239;\ 0{,}911;\ -1{,}021;\ 0{,}980] \\ u'_5 &= \mathrm{LN}(h_5) = [-1{,}102;\ 0{,}964;\ -0{,}892;\ 1{,}030] \\ u'_5 W_1 &= [u'_5\ |\ -u'_5] = [-1{,}102;\ 0{,}964;\ -0{,}892;\ 1{,}030;\ 1{,}102;\ -0{,}964;\ 0{,}892;\ -1{,}030] \\ \mathrm{GELU} &\to [-0{,}149;\ 0{,}802;\ -0{,}166;\ 0{,}874;\ 0{,}952;\ -0{,}162;\ 0{,}726;\ -0{,}156] \\ m_5 &= \mathrm{GELU}(\cdot)\, W_2 = [0{,}803;\ 0{,}640;\ 0{,}560;\ 0{,}718] \\ y_5 &= h_5 + m_5 = [-0{,}436;\ 1{,}551;\ -0{,}461;\ 1{,}698] \end{aligned}\]

Passo 9. O próximo token

Mais uma LayerNorm e depois o produto por \(W_U\) dá um score por token do vocabulário. Para ␣telhado:

\[f_5 = \mathrm{LN}(y_5) = [-0{,}987;\ 0{,}928;\ -1{,}011;\ 1{,}069],\quad z_{\text{telhado}} = f_5 \cdot W_U^{(6)} = -0{,}987 \cdot (-0{,}5) + 0{,}928 \cdot (1{,}0) + -1{,}011 \cdot (0{,}0) + 1{,}069 \cdot (1{,}0) = 2{,}491\]

O softmax transforma os 12 scores em probabilidades:

token \(z\) \(e^{z}\) \(p\)
␣telhado 2,491 12,069 0,395
␣muro 1,463 4,318 0,141
␣sofá 0,999 2,715 0,089
␣chão 0,970 2,637 0,086
␣gato 0,634 1,886 0,062
␣cão 0,564 1,757 0,058
␣no 0,418 1,519 0,050
iu 0,410 1,507 0,049
o 0,000 1,000 0,033
<eot> −0,518 0,596 0,020
␣sub −0,945 0,389 0,013
. −2,056 0,128 0,004

O mais provável é ␣telhado, com \(0{,}395\): a frase continua como "o gato subiu no telhado". A perda dele, \(-\ln 0{,}395 = 0{,}928\), é o \(L\) que o passo 2 usou para ajustar a tabela.


← Geração · próximo: O que mudou desde 2017 →