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\):
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 é
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 é
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:
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:
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:
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:
Passo 8. Primeira soma, LayerNorm e MLP
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:
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 →