Breno

Self-attention understood

So, what a great feeling. Self-attention foi compreendido.

Considere aqui o texto que considerei para explicar no notebook.

Para compreender, tem dois blocos de código. O modo de estabelecer a relação entre os tokens sem self-attention e o modo de estabelecer a relação entre os tokens com self-attention.

So, o primeiro bloco, que não tem self-attention seria o seguinte.

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

torch.manual_seed(1337);

B, T, C = 4, 8, 32 # batch, time, channels.

x = torch.randn(B, T, C)

tril = torch.tril(torch.ones(T, T))
wei = torch.zeros((T, T))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=-1)

So, texto explicando seria o seguinte.

O tril seria um modo de conseguir obter uma estrutura que ajude a chegar naquela regra de que o token não pode considerar um token futuro. Então para a primeira linha apenas o token em si; para o segundo token, pode ser ele mesmo e o anterior.

O wei seria a construção em si desses tokens e suas relações. Ele começa como uma matriz de T por T, que seriam os números de tokens, com zero, sem nenhuma informação.

Depois, aplica-se o tril como máscara considerando a condição de considerar um valor de menos infinito para quando o tril está com 0. Assim, chega-se na questão de apenas considerar o próprio token e os tokens passados.

O softmax depois seria aplicado ao wei para se obter números em intervalo que possa codificar melhor a relação dos tokens. No caso do resultado com o softmax já se chega em uma relação de média entre os tokens, como se a relação entre o token mesmo e os tokens passados tivesse a mesma importância.

Texto agora falando do self-attention.

O self-attention entra aqui. A relação entre os tokens pode não ser a mesma. Então essa média pode não representar a relação entre os tokens. Um token, para um dado token que se está considerando, um token do passado, pode ser melhor para o token que se está considerando do que um outro token do passado.

O self-attention realiza isso.

O Karpathy explica que o self-attention realiza isso com a emissão de dois sinais por token, a consulta e a chave.

O sinal de consulta significa o quê o token está procurando.

O sinal de chave signfica o quê o token possui.

O jeito que se obtém então afinidades entre os tokens seria com dot product entre os vetores de query e key.

So, para um exemplo. A consulta de um token dot product as chaves dos outros tokens se torna os pesos.

O x seria algo particular, então se tem uma boa afinidade, o que será mostrado seria o value, que seria outro sinal do self-attention mechanism.

Great, fim do texto, compreendi assim self-attention mechanism.

O código seria o seguinte.

# let's see a single Head perform self-attention.
head_size = 16
key = nn.Linear(C, head_size, bias=False)
query = nn.Linear(C, head_size, bias=False)
k = key(x) # (B, T, 16).
q = query(x) # (B, T, 16).
wei = q @ k.transpose(-2, -1) # (B, T, 16) @ (B, 16, T) results (B, T, T).

tril = torch.tril(torch.ones(T, T))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=1)
result = wei @ x

result.shape

Truly special de ter compreendido. Foco.

Sabe, fui assistindo o Karpathy e pausava, explicava no notebook com as minhas próprias palavras. Quando não entendi uma parte específica, discuti com o Claude, mas sabia a pergunta que queria realizar. That is important. To understand what you need to know. You ask to have a response. Clear question. Saber a pergunta a ser realizada.

Truly special.

Really great learning.