hacktricks/src/AI/AI-llm-architecture/4.-attention-mechanisms.md

422 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 4. Mehanizmi pažnje
{{#include ../../banners/hacktricks-training.md}}
## Mehanizmi pažnje i samopažnja u neuronskim mrežama
Mehanizmi pažnje omogućavaju neuronskim mrežama da **fokusiraju na specifične delove ulaza prilikom generisanja svakog dela izlaza**. Dodeljuju različite težine različitim ulazima, pomažući modelu da odluči koji su ulazi najrelevantniji za zadatak. Ovo je ključno u zadacima poput mašinskog prevođenja, gde je razumevanje konteksta cele rečenice neophodno za tačan prevod.
> [!TIP]
> Cilj ove četvrte faze je vrlo jednostavan: **Primeni neke mehanizme pažnje**. Ovi mehanizmi će biti mnogo **ponovljenih slojeva** koji će **uhvatiti odnos reči u rečniku sa njenim susedima u trenutnoj rečenici koja se koristi za obuku LLM-a**.\
> Za ovo se koristi mnogo slojeva, tako da će mnogo parametara za obuku hvatati ove informacije.
### Razumevanje mehanizama pažnje
U tradicionalnim modelima sekvenca-sekvenca koji se koriste za prevođenje jezika, model kodira ulaznu sekvencu u kontekstni vektor fiksne veličine. Međutim, ovaj pristup se suočava sa problemima sa dugim rečenicama jer fiksni kontekstni vektor možda neće uhvatiti sve potrebne informacije. Mehanizmi pažnje rešavaju ovo ograničenje omogućavajući modelu da razmatra sve ulazne tokene prilikom generisanja svakog izlaznog tokena.
#### Primer: Mašinsko prevođenje
Razmotrite prevođenje nemačke rečenice "Kannst du mir helfen diesen Satz zu übersetzen" na engleski. Prevod reč po reč ne bi proizveo gramatički ispravnu englesku rečenicu zbog razlika u gramatičkim strukturama između jezika. Mehanizam pažnje omogućava modelu da se fokusira na relevantne delove ulazne rečenice prilikom generisanja svake reči izlazne rečenice, što dovodi do tačnijeg i koherentnijeg prevoda.
### Uvod u samopažnju
Samopažnja, ili intra-pažnja, je mehanizam gde se pažnja primenjuje unutar jedne sekvence kako bi se izračunala reprezentacija te sekvence. Omogućava svakom tokenu u sekvenci da obraća pažnju na sve druge tokene, pomažući modelu da uhvati zavisnosti između tokena bez obzira na njihovu udaljenost u sekvenci.
#### Ključni koncepti
- **Tokeni**: Pojedinačni elementi ulazne sekvence (npr. reči u rečenici).
- **Umetanja**: Vektorske reprezentacije tokena, koje hvataju semantičke informacije.
- **Težine pažnje**: Vrednosti koje određuju važnost svakog tokena u odnosu na druge.
### Izračunavanje težina pažnje: Primer korak po korak
Razmotrimo rečenicu **"Hello shiny sun!"** i predstavimo svaku reč sa 3-dimenzionalnim umetanjima:
- **Hello**: `[0.34, 0.22, 0.54]`
- **shiny**: `[0.53, 0.34, 0.98]`
- **sun**: `[0.29, 0.54, 0.93]`
Naš cilj je da izračunamo **kontekstni vektor** za reč **"shiny"** koristeći samopažnju.
#### Korak 1: Izračunavanje rezultata pažnje
> [!TIP]
> Samo pomnožite svaku dimenzionalnu vrednost upita sa relevantnom vrednošću svakog tokena i saberite rezultate. Dobijate 1 vrednost po paru tokena.
Za svaku reč u rečenici, izračunajte **rezultat pažnje** u odnosu na "shiny" izračunavanjem skalarne produkcije njihovih umetanja.
**Rezultat pažnje između "Hello" i "shiny"**
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Rezultat pažnje između "shiny" i "shiny"**
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Rezultat pažnje između "sun" i "shiny"**
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
#### Korak 2: Normalizacija rezultata pažnje da bi se dobile težine pažnje
> [!TIP]
> Ne gubite se u matematičkim terminima, cilj ove funkcije je jednostavan, normalizujte sve težine tako da **ukupno sumiraju 1**.
>
> Pored toga, **softmax** funkcija se koristi jer naglašava razlike zbog eksponencijalnog dela, olakšavajući otkrivanje korisnih vrednosti.
Primeni **softmax funkciju** na rezultate pažnje da bi ih pretvorio u težine pažnje koje sumiraju 1.
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
Izračunavanje eksponencijala:
<figure><img src="../../images/image (4) (1) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
Izračunavanje sume:
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
Izračunavanje težina pažnje:
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
#### Korak 3: Izračunavanje kontekstnog vektora
> [!TIP]
> Samo uzmite svaku težinu pažnje i pomnožite je sa dimenzijama relevantnog tokena, a zatim saberite sve dimenzije da dobijete samo 1 vektor (kontekstni vektor)
**Kontekstni vektor** se izračunava kao ponderisana suma umetanja svih reči, koristeći težine pažnje.
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
Izračunavanje svake komponente:
- **Ponderisano umetanje "Hello"**:
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ponderisano umetanje "shiny"**:
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ponderisano umetanje "sun"**:
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
Saberanje ponderisanih umetanja:
`kontekstni vektor=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
**Ovaj kontekstni vektor predstavlja obogaćeno umetanje za reč "shiny", uključujući informacije iz svih reči u rečenici.**
### Sažetak procesa
1. **Izračunavanje rezultata pažnje**: Koristite skalarni proizvod između umetanja ciljne reči i umetanja svih reči u sekvenci.
2. **Normalizacija rezultata da bi se dobile težine pažnje**: Primeni softmax funkciju na rezultate pažnje da bi se dobile težine koje sumiraju 1.
3. **Izračunavanje kontekstnog vektora**: Pomnožite umetanje svake reči sa njenom težinom pažnje i saberite rezultate.
## Samopažnja sa težinama koje se mogu obučavati
U praksi, mehanizmi samopažnje koriste **težine koje se mogu obučavati** da bi naučili najbolje reprezentacije za upite, ključeve i vrednosti. Ovo uključuje uvođenje tri matrice težina:
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
Upit je podatak koji se koristi kao i ranije, dok su matrice ključeva i vrednosti samo nasumične matrice koje se mogu obučavati.
#### Korak 1: Izračunavanje upita, ključeva i vrednosti
Svaki token će imati svoju matricu upita, ključeva i vrednosti množenjem svojih dimenzionalnih vrednosti sa definisanim matricama:
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
Ove matrice transformišu originalna umetanja u novi prostor pogodan za izračunavanje pažnje.
**Primer**
Pretpostavljajući:
- Ulazna dimenzija `din=3` (veličina umetanja)
- Izlazna dimenzija `dout=2` (željena dimenzija za upite, ključeve i vrednosti)
Inicijalizujte matrice težina:
```python
import torch.nn as nn
d_in = 3
d_out = 2
W_query = nn.Parameter(torch.rand(d_in, d_out))
W_key = nn.Parameter(torch.rand(d_in, d_out))
W_value = nn.Parameter(torch.rand(d_in, d_out))
```
Izračunajte upite, ključeve i vrednosti:
```python
queries = torch.matmul(inputs, W_query)
keys = torch.matmul(inputs, W_key)
values = torch.matmul(inputs, W_value)
```
#### Step 2: Izračunavanje skalirane dot-produkta pažnje
**Izračunavanje ocena pažnje**
Slično prethodnom primeru, ali ovoga puta, umesto korišćenja vrednosti dimenzija tokena, koristimo ključnu matricu tokena (već izračunatu koristeći dimenzije):. Dakle, za svaku upit `qi` i ključ `kj`:
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
**Skaliranje ocena**
Da bismo sprečili da dot proizvodi postanu preveliki, skaliramo ih kvadratnim korenom dimenzije ključa `dk`:
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
> [!TIP]
> Ocena se deli kvadratnim korenom dimenzija jer dot proizvodi mogu postati veoma veliki i ovo pomaže u njihovom regulisanju.
**Primena Softmax-a za dobijanje težina pažnje:** Kao u inicijalnom primeru, normalizujte sve vrednosti tako da se saberu na 1.
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
#### Step 3: Izračunavanje kontekstualnih vektora
Kao u inicijalnom primeru, jednostavno saberite sve matrice vrednosti množeći svaku sa svojom težinom pažnje:
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
### Primer koda
Uzimajući primer sa [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) možete proveriti ovu klasu koja implementira funkcionalnost samopaznje o kojoj smo govorili:
```python
import torch
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
import torch.nn as nn
class SelfAttention_v2(nn.Module):
def __init__(self, d_in, d_out, qkv_bias=False):
super().__init__()
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.T
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
context_vec = attn_weights @ values
return context_vec
d_in=3
d_out=2
torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print(sa_v2(inputs))
```
> [!TIP]
> Imajte na umu da se umesto inicijalizacije matrica nasumičnim vrednostima, koristi `nn.Linear` da označi sve težine kao parametre za obučavanje.
## Uzročna Pažnja: Sakrivanje Budućih Reči
Za LLM-ove želimo da model uzima u obzir samo tokene koji se pojavljuju pre trenutne pozicije kako bi **predvideo sledeći token**. **Uzročna pažnja**, takođe poznata kao **maskirana pažnja**, to postiže modifikovanjem mehanizma pažnje kako bi se sprečio pristup budućim tokenima.
### Primena Maski za Uzročnu Pažnju
Da bismo implementirali uzročnu pažnju, primenjujemo masku na rezultate pažnje **pre softmax operacije** tako da preostali rezultati i dalje sumiraju 1. Ova maska postavlja rezultate pažnje budućih tokena na negativnu beskonačnost, osiguravajući da nakon softmax-a, njihova težina pažnje bude nula.
**Koraci**
1. **Izračunavanje Rezultata Pažnje**: Isto kao i pre.
2. **Primena Maske**: Koristite gornju trougaastu matricu ispunjenu negativnom beskonačnošću iznad dijagonale.
```python
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
masked_scores = attention_scores + mask
```
3. **Primena Softmax-a**: Izračunajte težine pažnje koristeći maskirane rezultate.
```python
attention_weights = torch.softmax(masked_scores, dim=-1)
```
### Maskiranje Dodatnih Težina Pažnje sa Dropout-om
Da bismo **sprečili prekomerno učenje**, možemo primeniti **dropout** na težine pažnje nakon softmax operacije. Dropout **nasumično postavlja neke od težina pažnje na nulu** tokom obučavanja.
```python
dropout = nn.Dropout(p=0.5)
attention_weights = dropout(attention_weights)
```
Redovni dropout je oko 10-20%.
### Code Example
Code example from [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb):
```python
import torch
import torch.nn as nn
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
batch = torch.stack((inputs, inputs), dim=0)
print(batch.shape)
class CausalAttention(nn.Module):
def __init__(self, d_in, d_out, context_length,
dropout, qkv_bias=False):
super().__init__()
self.d_out = d_out
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # This generates the keys of the tokens
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.transpose(1, 2) # Moves the third dimension to the second one and the second one to the third one to be able to multiply
attn_scores.masked_fill_( # New, _ ops are in-place
self.mask.bool()[:num_tokens, :num_tokens], -torch.inf) # `:num_tokens` to account for cases where the number of tokens in the batch is smaller than the supported context_size
attn_weights = torch.softmax(
attn_scores / keys.shape[-1]**0.5, dim=-1
)
attn_weights = self.dropout(attn_weights)
context_vec = attn_weights @ values
return context_vec
torch.manual_seed(123)
context_length = batch.shape[1]
d_in = 3
d_out = 2
ca = CausalAttention(d_in, d_out, context_length, 0.0)
context_vecs = ca(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
## Proširenje jedne glave pažnje na višestruku glavu pažnje
**Višestruka pažnja** u praktičnom smislu se sastoji od izvršavanja **više instanci** funkcije samopaznje, svaka sa **svojim težinama**, tako da se izračunavaju različiti konačni vektori.
### Primer koda
Moguće je ponovo koristiti prethodni kod i samo dodati omotač koji ga pokreće nekoliko puta, ali ovo je optimizovana verzija iz [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) koja obrađuje sve glave u isto vreme (smanjujući broj skupih for petlji). Kao što možete videti u kodu, dimenzije svake oznake su podeljene u različite dimenzije prema broju glava. Na ovaj način, ako oznaka ima 8 dimenzija i želimo da koristimo 3 glave, dimenzije će biti podeljene u 2 niza od 4 dimenzije, a svaka glava će koristiti jedan od njih:
```python
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert (d_out % num_heads == 0), \
"d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer(
"mask",
torch.triu(torch.ones(context_length, context_length),
diagonal=1)
)
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
torch.manual_seed(123)
batch_size, context_length, d_in = batch.shape
d_out = 2
mha = MultiHeadAttention(d_in, d_out, context_length, 0.0, num_heads=2)
context_vecs = mha(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
Za još jednu kompaktno i efikasnu implementaciju možete koristiti [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) klasu u PyTorch-u.
> [!TIP]
> Kratak odgovor ChatGPT-a o tome zašto je bolje podeliti dimenzije tokena među glavama umesto da svaka glava proverava sve dimenzije svih tokena:
>
> Dok bi omogućavanje svakoj glavi da obrađuje sve dimenzije ugrađivanja moglo izgledati korisno jer bi svaka glava imala pristup punim informacijama, standardna praksa je da se **podele dimenzije ugrađivanja među glavama**. Ovaj pristup balansira računarsku efikasnost sa performansama modela i podstiče svaku glavu da uči raznolike reprezentacije. Stoga, deljenje dimenzija ugrađivanja se generalno preferira u odnosu na to da svaka glava proverava sve dimenzije.
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
{{#include ../../banners/hacktricks-training.md}}