01 - Mecanismul de atenție în Transformers: Ghid complet
În 2017, o lucrare Google Brain intitulată „Atenția este tot ce ai nevoie” a schimbat lucrurile pentru totdeauna domeniul învățării profunde. Autorii, Vaswani și colegii, au propus o arhitectură în întregime bazat pe un mecanism numit Atenţie, eliminând rețelele sisteme recurente (RNN) și convoluționale care dominau până atunci. Rezultatul a fost arhitectura Transformatoare, astăzi la baza GPT-4, Claude, Llama 3, BERT, T5, Vision Transformers și aproape orice model de frontieră.
Înțelegerea mecanismului atenției nu este un exercițiu academic: este fundamentul pe care se află ei construiesc tehnici precum reglajul fin, cuantizarea, tăierea și implementarea LoRA dispozitive edge, toate subiectele pe care le vom aborda în această serie. Fără o înțelegere înțelegere solidă a modului în care funcționează atenția, fiecare optimizare ulterioară rămâne o cutie neagră.
În acest prim articol al seriei Învățare profundă avansată și implementare Edge, vom explora atenția în profunzime: de la intuiția inițială până la formula matematică, de la implementarea în PyTorch la variante moderne precum Flash Attention 3 și Grouped-Query Atenție.
Prezentare generală a seriei
| # | Articol | Concentrează-te |
|---|---|---|
| 1 | Sunteți aici - Mecanismul de atenție în Transformers | Autoatenție, multi-capete, arhitectură completă |
| 2 | Reglaj fin cu LoRA, QLoRA și adaptoare | Reglare fină eficientă din punct de vedere al parametrilor |
| 3 | Cuantificarea modelelor | INT8, INT4, GPTQ, AWQ |
| 4 | Tunderea și compresia | Reducerea parametrilor, distilare |
| 5 | Distilarea Cunoașterii | Profesor-elev, transfer de cunoștințe |
| 6 | Ollama și LLM Local | Inferență locală, optimizare |
| 7 | Transformator de vedere | ViT, DINO, clasificare imagini |
| 8 | Implementare Edge | ONNX, TensorRT, dispozitive mobile |
| 9 | NAS și AutoML | Căutarea arhitecturii neuronale |
| 10 | Benchmarking și optimizare | Profilare, metrici, reglare |
Ce vei învăța
- deoarece RNN-urile și LSTM-urile nu erau suficiente pentru secvențe lungi
- Intuiția din spatele mecanismului de atenție: interogare, cheie și valoare
- Formula completă de Atenție a produsului punctat la scară
- Cum funcționează atenția cu mai multe capete și de ce aveți nevoie de mai multe capete
- Diferența dintre atenția de sine și atenția încrucișată
- Cum rezolvă codificarea pozițională problema comenzii
- Arhitectura Transformer completă: codificator și decodor
- Implementare practică în PyTorch, linie cu linie
- Variante moderne: Flash Attention 3, GQA, Sliding Window Attention
- Arhitecturile reale: GPT (doar decodor), BERT (doar codificator), T5 (codor-decodor)
1. Problema secvenței: înainte de atenție
Pentru a înțelege de ce atenția a fost o revoluție, trebuie să plecăm de la modelele care au precedat-o. Învățarea profundă pentru secvențe (text, audio, serii cronologice) a fost dominată de două arhitecturi: cel RNN (rețele neuronale recurente) iar cel LSTM (Memorie pe termen lung).
1.1 RNN-urile și blocajul secvenţial
RNN-urile procesează secvențele câte un jeton, trecând o stare ascunsă de la un pas de timp la altul. Fiecare token actualizează starea ascunsă, în care acționează „memoria” secvenței văzute până acum.
Input: x1 -----> x2 -----> x3 -----> x4 -----> x5
| | | | |
v v v v v
Hidden: h1 -----> h2 -----> h3 -----> h4 -----> h5
| |
v v
Output: y1 y5
Problema: h5 deve "ricordare" x1 attraverso 4 passaggi.
Con sequenze di 1000+ token, l'informazione di x1 svanisce.
Aceasta este problema dependențe pe termen lung. Într-o propoziție de genul „Pisica, care fusese adoptată de la adăpost acum trei ani și trăia fericit cu familia, el dormea pe canapea”, RNN trebuie să conecteze „pisica” un „adormit” prin zeci de jetoane intermediare. Starea ascunsă, comprimată într-un vector de dimensiune fixă, pierde inevitabil informații mai vechi.
1.2 LSTM: o îmbunătățire, nu o soluție
LSTM-urile au introdus un mecanism de poartă (poartă de intrare, poartă uitare, poartă de ieșire) pentru controlați ce informații să păstrați și pe care să le aruncați. Acest lucru a îmbunătățit situația, dar nu a rezolvat-o. LSTM-urile încă suferă de două probleme fundamentale:
Limitările RNN/LSTM-urilor
| Problemă | Descriere | Impact |
|---|---|---|
| Secvenţialitatea | Fiecare jeton depinde de cel precedent: nu poate fi paralelizat | Antrenament foarte lent pe secvențe lungi |
| Gâtul de sticlă | Toate informațiile trec printr-un singur transportator | Pierdere de informații cu secvențe > 100-200 de jetoane |
| Gradient dispare | Gradienții se micșorează exponențial în timpul propagării inverse | Modelul nu reușește să învețe relațiile îndepărtate |
Aveam nevoie de un mecanism care să permită accesul fiecărui token direct a orice alt simbol din secvență, fără a fi nevoie să treacă prin stări intermediare. Acest mecanism șiAtenţie.
2. Ce este Atenția: Intuiția
Atenția este un mecanism care permite unui model să concentrează-te pe a ta fii atent pe cele mai relevante părți ale intrării la generarea ieșirii. În schimb pentru a comprima întreaga secvență într-un singur vector, atenția creează o conexiune direct între fiecare poziţie de ieşire şi toate poziţiile de intrare.
Analogie: Căutarea într-o bibliotecă
Imaginează-ți că ești într-o librărie și cauți informații despre „istoria Transformers”. Ai unul în minte cerere (Interogări). Fiecare carte are un titlu (Cheie) care descrie conținutul acestuia. Când titlul se potrivește cu întrebarea dvs., extrage conţinut (Valoarea) acelei cărți. Atentie functioneaza exact asa:
- Interogare (Q): "Ce caut?" - întrebarea pe care o pune jetonul curent
- Tasta (K): „Ce conține acest articol?” - eticheta fiecărui jeton din secvență
- Valoare (V): „Iată informațiile” – conținutul real al fiecărui token
Mecanismul calculează a scor de compatibilitate între Interogare și fiecare Cheie. Acest scor determină cât de multă atenție trebuie acordată valorii corespunzătoare. Vin scorurile normalizat prin softmax pentru a obține ponderi care însumează 1, iar rezultatul final este unul media ponderată a Valorilor.
Token corrente: "dormiva"
Query di "dormiva": "Chi sta compiendo questa azione?"
Key Score Peso (softmax)
"Il" -----> 0.1 0.02
"gatto" -----> 4.8 0.65 <-- Alta attenzione!
"che" -----> 0.3 0.03
"era" -----> 0.2 0.02
"stato" -----> 0.1 0.02
"adottato" -----> 1.2 0.08
"..." -----> ... ...
"sul" -----> 2.1 0.12
"divano" -----> 0.8 0.06
Output = 0.02 * V("Il") + 0.65 * V("gatto") + 0.03 * V("che") + ...
Il modello ha imparato che "gatto" e il soggetto di "dormiva",
anche se sono separati da molti token.
3. Atenție la scară de produs punct: Formula
Formularea matematică a atenției utilizată în Transformers și în Scalate Dot-Produs Atenție. Este elegant prin simplitate și din punct de vedere computațional eficient datorita utilizarii operatiilor matriceale.
Formula Atenției
Atenție(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V
Unde:
- Q (Interogare): matrice de dimensiune (n x d_k), unde n este numărul de jetoane și d_k este dimensiunea interogărilor/cheii
- K (Cheie): matrice de dimensiuni (n x d_k)
- V (Valoare): matricea dimensiunii (n x d_v), unde d_v este dimensiunea valorilor
- d_k: dimensiunea tastei, folosită ca factor de scalare
- Q * K^T: produs scalar între interogare și cheie (n x n matrice de scor)
- / sqrt(d_k): factor de scalare pentru a stabiliza gradienții
- softmax: normalizează scorurile în ponderi care însumează 1
3.1 De ce este necesară scalarea
Fără factor sqrt(d_k), produsul scalar dintre Q și K produce valori crescătoare
proporţional cu dimensiunea d_k. Cu d_k = 512, produsele scalare pot ajunge
valori foarte mari. Când aceste valori ajung în softmax, ele produc distribuții
aproape unul fierbinte (o greutate aproape de 1, toate celelalte aproape de 0), cu gradiente extrem de mici.
Scalare previne această problemă.
Senza scaling (d_k = 512):
Score raw: [120.3, 115.8, 2.1, -5.4]
Softmax: [0.989, 0.011, 0.000, 0.000] <-- Quasi one-hot, gradienti ~0
Con scaling (/ sqrt(512) = / 22.6):
Score scaled: [5.32, 5.12, 0.09, -0.24]
Softmax: [0.44, 0.36, 0.10, 0.10] <-- Distribuzione morbida, gradienti sani
3.2 Pas cu pas: calculul atenției
Să vedem un exemplu numeric concret cu o secvență de 3 jetoane și d_k = 4:
Sequenza: ["The", "cat", "sat"]
Step 1: Genera Q, K, V tramite proiezioni lineari
Q = X * W_Q K = X * W_K V = X * W_V
Q = [[1.0, 0.5, 0.3, 0.2], (The)
[0.8, 1.2, 0.1, 0.9], (cat)
[0.3, 0.4, 1.1, 0.6]] (sat)
K = [[0.9, 0.6, 0.4, 0.1],
[0.7, 1.1, 0.2, 0.8],
[0.4, 0.3, 1.0, 0.5]]
V = [[0.2, 0.8, 0.1, 0.5],
[0.9, 0.3, 0.7, 0.2],
[0.4, 0.6, 0.5, 0.8]]
Step 2: Calcola Q * K^T (matrice 3x3 di score)
Score[i][j] = dot(Q[i], K[j])
Scores = [[1.19, 1.37, 0.89],
[1.35, 1.77, 1.10],
[0.98, 1.15, 1.42]]
Step 3: Scala per sqrt(d_k) = sqrt(4) = 2
Scaled = [[0.60, 0.69, 0.45],
[0.68, 0.89, 0.55],
[0.49, 0.58, 0.71]]
Step 4: Applica softmax per riga
Weights = [[0.33, 0.36, 0.31], (The guarda The, cat, sat)
[0.32, 0.40, 0.28], (cat guarda The, cat, sat)
[0.29, 0.32, 0.39]] (sat guarda The, cat, sat)
Step 5: Moltiplica pesi per V
Output[0] = 0.33*V[0] + 0.36*V[1] + 0.31*V[2]
= [0.51, 0.56, 0.39, 0.48]
Atentie la Complexitate
Matricea Q * K^T are dimensiune n x n, unde n este lungimea secvenței. Cu n = 1000, matricea are 1.000.000 de elemente. Cu n = 100.000, are 10 miliarde de elemente. Această complexitate pătratică O(n^2) este principalul blocaj al Transformers și motivul pentru care au fost dezvoltate variante precum Flash Attention și Sliding Window Attention.
4. Atenție cu mai multe capete: vizionați din mai multe unghiuri
O singură operație de atenție surprinde un tip de relație între jetoane. Dar relațiile într-o secvență sunt multiple: relații sintactice (subiect-verb), semantice (sinonime, context), poziționale (jetoane adiacente) și multe altele. Acolo Atenție cu mai multe capete rezolvă această problemă prin efectuarea atenției în paralel cu diferite proiecții.
Input X (dimensione: n x d_model, es. n x 512)
|
+---> Head 1: Q1=X*Wq1, K1=X*Wk1, V1=X*Wv1 --> Attention(Q1,K1,V1) --> Z1
| (d_k = d_model/h = 64)
+---> Head 2: Q2=X*Wq2, K2=X*Wk2, V2=X*Wv2 --> Attention(Q2,K2,V2) --> Z2
|
+---> Head 3: Q3=X*Wq3, K3=X*Wk3, V3=X*Wv3 --> Attention(Q3,K3,V3) --> Z3
|
+---> ...
|
+---> Head 8: Q8=X*Wq8, K8=X*Wk8, V8=X*Wv8 --> Attention(Q8,K8,V8) --> Z8
|
v
Concatena: [Z1; Z2; Z3; ... Z8] (dimensione: n x d_model)
|
v
Proiezione finale: Concat * W_O (dimensione: n x d_model)
Cu h = 8 capete şi d_model = 512, fiecare cap lucrează pe unul
spațiu dimensional d_k = d_v = 512 / 8 = 64. Costul total de calcul
Este asemănător cu cel al unei singure atenții cu dimensiune completă, deoarece capetele funcționează
în paralel pe subspații mai mici.
Ce învață fiecare cap
Cercetările empirice au arătat că diferite capete se specializează în diferite modele:
- Cap 1: Ar putea învăța relațiile subiect-verb
- Cap 2: Ar putea învăța relații de coreferență (pronumele și antecedentele lor)
- Cap 3: S-ar putea concentra pe jetoane adiacente (n-grame locale)
- Cap 4: Ar putea surprinde relații pe termen lung între propoziții
- Alte capete: Modele sintactice, entitati, structura discursului
Formula de atenție cu mai multe capete
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) * W_O
Unde head_i = Atenție (Q * W_Qi, K * W_Ki, V * W_Vi)
Parametri tipici din lucrarea originală: d_model = 512, h = 8, d_k = d_v = 64. În modelele moderne: d_model = 4096-8192, h = 32-128.
5. Auto-atenție: un simbol care îi urmărește pe toți ceilalți
La autoatenție și cazul specific de unde provin Interogarea, Cheia și Valoarea toate din aceeași succesiune. Fiecare token generează propria sa interogare, cheie și valoare și folosește interogarea pentru a „interoga” Cheile tuturor celorlalte jetoane (inclusiv el însuși).
Frase: "The cat sat on the mat"
Attention Matrix (ogni riga somma a 1.0):
The cat sat on the mat
The [0.15 0.25 0.10 0.05 0.15 0.30]
cat [0.10 0.20 0.35 0.05 0.05 0.25]
sat [0.05 0.40 0.15 0.20 0.05 0.15]
on [0.05 0.10 0.30 0.10 0.15 0.30]
the [0.20 0.15 0.05 0.10 0.10 0.40]
mat [0.10 0.15 0.15 0.25 0.15 0.20]
Osservazioni:
- "sat" presta molta attenzione a "cat" (0.40) --> soggetto-verbo
- "on" presta attenzione a "sat" (0.30) e "mat" (0.30) --> relazione spaziale
- "the" (seconda occorrenza) presta molta attenzione a "mat" (0.40) --> articolo-sostantivo
Autoatenția este inima Transformers. Și ceea ce permite modelului să se construiască reprezentări contextuale: Reprezentarea fiecărui jeton încorporat informații din întreaga secvență, ponderate după relevanță. Cuvântul „bancă” va avea o reprezentare diferită în „bankul fluviului” și „cont bancar” deoarece jetoanele din jur influenţează reprezentarea acestuia prin atenţie.
Auto-atenție mascată în decodoare
În modelele generative (decodificatoare), autoatenția e mascaradă: fiecare token-ul poate vedea doar jetoanele anterioare, nu pe cele viitoare. Acest lucru este implementat setând scorurile viitoarelor jetoane la -infinit înainte de softmax, producând greutăți egale cu zero. Acesta este atenție cauzală folosit în GPT, Llama și toate modelele autoregresive.
Mask per sequenza di 5 token (0 = visibile, -inf = mascherato):
t1 t2 t3 t4 t5
t1 [ 0 -inf -inf -inf -inf ]
t2 [ 0 0 -inf -inf -inf ]
t3 [ 0 0 0 -inf -inf ]
t4 [ 0 0 0 0 -inf ]
t5 [ 0 0 0 0 0 ]
Dopo la softmax:
t1 vede solo [t1]
t2 vede solo [t1, t2]
t3 vede solo [t1, t2, t3]
...e cosi via
6. Atenție încrucișată: atunci când codificatoarele și decodoarele comunică
La atenție încrucișată (sau atenție codificator-decodor) și mecanismul care permite decodorului să „vizioneze” ieșirea codificatorului. Spre deosebire de autoatenție, unde Q, K și V provin din aceeași secvență, în atenție încrucișată provin Interogările de la decodor și Cheia/Valoarea de la encoder.
ENCODER (processa l'input, es. frase in italiano):
"Il gatto dorme" --> Encoder --> Rappresentazioni encoder (K_enc, V_enc)
DECODER (genera l'output, es. traduzione in inglese):
"The cat" --> Self-Attention mascherata --> Q_dec
CROSS-ATTENTION:
Q = Q_dec (dal decoder: "cosa sto cercando per generare il prossimo token?")
K = K_enc (dall'encoder: "cosa contiene ogni token dell'input?")
V = V_enc (dall'encoder: "ecco le informazioni dell'input")
Il decoder può "guardare" tutta la sequenza dell'encoder
per decidere quale token generare dopo.
Atenția încrucișată este fundamentală în arhitectură codificator-decodor folosit pentru traducerea automată (T5, mBART), rezumarea textului și generarea condiționată. În T5, de exemplu, codificatorul procesează textul de intrare, iar decodorul generează textul ieșire, folosind atenția încrucișată pentru a consulta codificatorul la fiecare pas de generare.
Cele trei tipuri de atenție în transformatoare
| Tip | sursa Q | Sursa K, V | Unde să-l folosești |
|---|---|---|---|
| Auto-atenție (encoder) | Intrări pentru codificator | Intrări pentru codificator | Encoder BERT, encoder T5 |
| Auto-atenție mascată | Decodor de intrare | Decodor de intrare | GPT, Llama, decodor T5 |
| Atenție încrucișată | Decodoare | Ieșire codificator | Decodor T5, mBART |
7. Codificarea pozițională: Cum Transformers cunosc ordinea
Spre deosebire de RNN, care procesează token-urile în ordine secvențială, autoatenție e invariant în raport cu ordinea: rezultatul nu se schimbă dacă permutați jetoanele de intrare. „Pisica mănâncă peștele” și „Pisica mănâncă peștele” ar produce aceeași ieșire fără un mecanism suplimentar. The pozițional codificare rezolvă această problemă adăugând informații despre locația fiecare jeton.
7.1 Codificare pozițională sinusoidală (Hârtie originală)
Lucrarea originală folosește funcții sinusoidale pentru a genera codificări poziționale:
Formule de codificare pozițională sinusoidală
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(poz, 2i+1) = cos(poz / 10000^(2i/d_model))
Unde poz și poziția jetonului în secvența e i si marimea. Pozițiile pare folosesc sinus, pozițiile impare folosesc cosinus. Frecvența diferită pentru fiecare dimensiunea permite modelului să învețe relații de poziție relative.
Posizione 0: [sin(0), cos(0), sin(0), cos(0), ...] = [0.00, 1.00, 0.00, 1.00, ...]
Posizione 1: [sin(1), cos(1), sin(0.01), cos(0.01)] = [0.84, 0.54, 0.01, 1.00, ...]
Posizione 2: [sin(2), cos(2), sin(0.02), cos(0.02)] = [0.91, -0.42, 0.02, 1.00, ...]
L'embedding finale di ogni token e:
token_embedding = word_embedding + positional_encoding
Le frequenze più basse (dimensioni alte) catturano posizioni globali.
Le frequenze più alte (dimensioni basse) catturano posizioni locali.
7.2 Codificarea pozițională învățată
O alternativă la codificarea sinusoidală pozițională și utilizarea înglobări învăţate (invatat): o serie de parametri antrenați, un rând pentru fiecare poziție. Această abordare este utilizată în BERT și GPT-2. Avantajul este că modelul poate învăța modele poziționale optime pentru sarcina specifică. Dezavantajul este că lungimea maxim al secvenței și fixat la antrenament.
Comparație de codificare pozițională
| Tip | Avantaje | Dezavantaje | Folosit în |
|---|---|---|---|
| Sinusoidal | Fără parametri suplimentari, se generalizează la secvențe mai lungi | Modele fixe, neoptimizate pentru sarcină | Transformator original |
| Învăţat | Optimizat pentru sarcina specifică | Lungime maximă fixă, parametri multipli | BERT, GPT-2 |
| Funie (rotativă) | Capturați poziții relative, extensibile | Complexitate mai mare de implementare | Lama, Mistral, GPT-NeoX |
| Alibi | Fără parametri, extrapolare bună | Prejudecățile liniare pot fi limitative | BLOOM, MPT |
8. Arhitectura completă a transformatorului
Cu toate piesele puzzle-ului în mână, acum putem asambla arhitectura Transformerului completă. Transformerul original este format din a stiva codificatoare si a decodor stivă, fiecare alcătuit din N straturi identice (N = 6 în hârtie original).
INPUT EMBEDDING + POSITIONAL ENCODING
|
+---------v-----------+
| ENCODER STACK | x N (6 nel paper originale)
| |
| +--Multi-Head-------+
| | Self-Attention |
| +------|------------+
| v
| +--Add & Norm-------+ (residual connection + layer norm)
| +------|------------+
| v
| +--Feed-Forward-----+ (2 layer lineari con ReLU/GELU)
| | Network | (d_model -> d_ff -> d_model)
| +------|------------+ (d_ff = 4 * d_model = 2048)
| v
| +--Add & Norm-------+
| +------|------------+
+---------|-----------+
|
| (K, V per cross-attention)
|
OUTPUT EMBEDDING + POSITIONAL ENCODING
|
+---------v-----------+
| DECODER STACK | x N
| |
| +--Masked Multi-----+
| | Head Self-Attn | (causal mask: vede solo il passato)
| +------|------------+
| v
| +--Add & Norm-------+
| +------|------------+
| v
| +--Cross-Attention--+ (Q dal decoder, K/V dall'encoder)
| +------|------------+
| v
| +--Add & Norm-------+
| +------|------------+
| v
| +--Feed-Forward-----+
| +------|------------+
| v
| +--Add & Norm-------+
| +------|------------+
+---------|-----------+
|
v
Linear + Softmax
|
v
Output Probabilities (vocabulario)
8.1 Conexiuni reziduale
Fiecare sub-strat (atenție sau feed-forward) are un conexiune reziduală:
ieșirea sub-stratului este adăugată la intrare. Formula e
output = LayerNorm(x + SubLayer(x)). Conexiunile reziduale rezolvă problema
problema de gradient care dispare în rețelele adânci, permițând curgerea gradientului
direct prin conexiuni rapide.
8.2 Rețea de tip Feed-Forward
După atenție, fiecare jeton trece prin a rețea feed-forward aplicat independent fiecărei poziții. Este compus din două transformări liniare cu o activare neliniară (ReLU în lucrarea originală, GELU sau SwiGLU în modele modern):
FFN(x) = W2 * activare(W1 * x + b1) + b2
Dimensiunea internă (d_ff) este de obicei de 4 ori d_model. Cu d_model = 512, d_ff = 2048. În modelele moderne precum Llama 3, d_ff urcă până la 14.336 cu d_model = 4096.
8.3 Normalizarea stratului
La Normalizarea stratului normalizează activările de-a lungul dimensiunii a caracteristicilor (nu a lotului). Stabilizează antrenamentul și accelerează convergența. În originalul Transformer Post-LN este utilizat (normalizare după conexiunea reziduală), dar majoritatea modelelor moderne folosesc Pre-LN (normalizare înainte de sub-strat), care este mai stabil în timpul antrenamentului.
9. Implementarea PyTorch: Auto-atenție de la zero
Să trecem de la teorie la cod. Vom implementa Scaled Dot-Product Atention și Multi-Head Atenție de la zero în PyTorch, fără a utiliza module pre-construite.
9.1 Atenție la scară de produs punct
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
def scaled_dot_product_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
mask: torch.Tensor = None,
dropout: nn.Dropout = None
) -> tuple[torch.Tensor, torch.Tensor]:
"""
Scaled Dot-Product Attention.
Args:
query: (batch, heads, seq_len, d_k)
key: (batch, heads, seq_len, d_k)
value: (batch, heads, seq_len, d_v)
mask: (batch, 1, 1, seq_len) o (batch, 1, seq_len, seq_len)
dropout: modulo dropout opzionale
Returns:
output: (batch, heads, seq_len, d_v)
attention_weights: (batch, heads, seq_len, seq_len)
"""
d_k = query.size(-1)
# Step 1: Calcola gli score Q * K^T / sqrt(d_k)
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
# Step 2: Applica la maschera (opzionale)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# Step 3: Softmax per ottenere i pesi di attention
attention_weights = F.softmax(scores, dim=-1)
# Step 4: Dropout opzionale sui pesi
if dropout is not None:
attention_weights = dropout(attention_weights)
# Step 5: Moltiplica pesi per Value
output = torch.matmul(attention_weights, value)
return output, attention_weights
9.2 Atenție cu mai multe capete
class MultiHeadAttention(nn.Module):
"""
Multi-Head Attention implementata da zero.
Parametri:
d_model: dimensione del modello (es. 512)
num_heads: numero di teste di attention (es. 8)
dropout: tasso di dropout (es. 0.1)
"""
def __init__(self, d_model: int, num_heads: int, dropout: float = 0.1):
super().__init__()
assert d_model % num_heads == 0, \
f"d_model ({d_model}) deve essere divisibile per num_heads ({num_heads})"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # dimensione per testa
# Proiezioni lineari per Q, K, V e output
self.w_q = nn.Linear(d_model, d_model, bias=False)
self.w_k = nn.Linear(d_model, d_model, bias=False)
self.w_v = nn.Linear(d_model, d_model, bias=False)
self.w_o = nn.Linear(d_model, d_model, bias=False)
self.dropout = nn.Dropout(dropout)
def split_heads(self, x: torch.Tensor) -> torch.Tensor:
"""
Riorganizza il tensore da (batch, seq_len, d_model)
a (batch, num_heads, seq_len, d_k).
"""
batch_size, seq_len, _ = x.size()
x = x.view(batch_size, seq_len, self.num_heads, self.d_k)
return x.transpose(1, 2) # (batch, heads, seq_len, d_k)
def forward(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
mask: torch.Tensor = None
) -> torch.Tensor:
"""
Forward pass.
Per Self-Attention: query = key = value = X
Per Cross-Attention: query = decoder, key = value = encoder
"""
batch_size = query.size(0)
# 1. Proiezioni lineari
q = self.w_q(query) # (batch, seq_len, d_model)
k = self.w_k(key)
v = self.w_v(value)
# 2. Dividi in teste
q = self.split_heads(q) # (batch, heads, seq_len, d_k)
k = self.split_heads(k)
v = self.split_heads(v)
# 3. Scaled Dot-Product Attention
attn_output, attn_weights = scaled_dot_product_attention(
q, k, v, mask=mask, dropout=self.dropout
)
# 4. Concatena le teste
# (batch, heads, seq_len, d_k) -> (batch, seq_len, d_model)
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, -1, self.d_model)
# 5. Proiezione finale
output = self.w_o(attn_output)
return output
9.3 Exemplu de utilizare
# Configurazione
batch_size = 2
seq_len = 10
d_model = 512
num_heads = 8
# Crea il modulo
mha = MultiHeadAttention(d_model=d_model, num_heads=num_heads)
# Input random (simula una sequenza di token embeddings)
x = torch.randn(batch_size, seq_len, d_model)
# Self-Attention (query = key = value)
output = mha(query=x, key=x, value=x)
print(f"Input shape: {x.shape}") # torch.Size([2, 10, 512])
print(f"Output shape: {output.shape}") # torch.Size([2, 10, 512])
# Causal mask per decoder (triangolare inferiore)
causal_mask = torch.tril(torch.ones(seq_len, seq_len))
causal_mask = causal_mask.unsqueeze(0).unsqueeze(0) # (1, 1, seq_len, seq_len)
# Masked Self-Attention
output_masked = mha(query=x, key=x, value=x, mask=causal_mask)
print(f"Masked output shape: {output_masked.shape}")
# Cross-Attention (query dal decoder, key/value dall'encoder)
encoder_output = torch.randn(batch_size, 20, d_model) # sequenza encoder più lunga
decoder_input = torch.randn(batch_size, seq_len, d_model)
cross_attn_output = mha(
query=decoder_input,
key=encoder_output,
value=encoder_output
)
print(f"Cross-attention shape: {cross_attn_output.shape}") # [2, 10, 512]
10. Variații moderne ale atenției
Complexitatea pătratică O(n^2) a atenției standard a motivat dezvoltarea numeroase variante optimizate. Aceste variații sunt fundamentale pentru modelele moderne gestionarea contextelor de la 100K la peste 1 milion de jetoane.
10.1 Atenție flash (v1, v2, v3)
Flash Atenție, dezvoltat de Tri Dao și colegii săi, nu schimbă matematica de atenție dar își optimizează radical implementarea la nivel hardware. Ideea cheie si evita materializarea matricei complete n x n a punctajelor de atentie in Memorie GPU (HBM), folosind în schimb o singură abordare gresie care lucrează în întregime în SRAM (memorie rapidă pe cip).
Evoluția atenției flash
| Versiune | An | Inovație cheie | Performanţă |
|---|---|---|---|
| Atenție flash 1 | 2022 | Tiling + nucleu fuzionat, IO-awareness | Accelerare de 2-4 ori față de standard |
| Atenție flash 2 | 2023 | Paralelism îmbunătățit, mai puțină comunicare | de 2 ori mai departe decât v1 |
| Atenție flash 3 | 2024 | Asynchrony pe Hopper GPU, FP8, specializare warp | Până la 740 TFLOPS (FP16) pe H100, 1,2 PFLOPS cu FP8 |
Flash Attention 3 profită de caracteristicile specifice ale GPU-urilor NVIDIA Hopper (H100/H200): asincronie între Tensor Core și TMA (Tensor Memory Accelerator) pentru a se suprapune calcul și transfer de date, specializarea warp pentru intercalare optim pentru operațiuni matmul și softmax, de ex Cuantificare bloc FP8 cu o eroare numerică de 2,6 ori mai mică decât o implementare naivă FP8. Flash Atenția este acum integrată în PyTorch, Hugging Face Transformers, vLLM și TensorRT-LLM.
10.2 Atenție la mai multe interogări (MQA)
Propus de Shazeer în 2019, the Atenție la mai multe interogări reduce drastic memoria necesară pentru cache-ul KV în timpul inferenței. În loc să ai un set separat de cheie și valoare pentru fiecare șef, MQA acționează a singur set de K și V dintre toate capete, menținând diferite Interogări.
Multi-Head Attention (MHA) - Standard:
Head 1: Q1, K1, V1 | KV Cache per head: d_k * seq_len * 2
Head 2: Q2, K2, V2 | KV Cache totale: h * d_k * seq_len * 2
... | Con h=32, d_k=128, seq=4096:
Head h: Qh, Kh, Vh | = 32 * 128 * 4096 * 2 = 33.5 MB per layer
Multi-Query Attention (MQA):
Head 1: Q1 \
Head 2: Q2 |--- K_shared, V_shared
... | KV Cache totale: d_k * seq_len * 2
Head h: Qh / = 128 * 4096 * 2 = 1.05 MB per layer (32x meno!)
10.3 Atenție interogări grupate (GQA)
GQA, introdus de Ainslie et al. în 2023 și un compromis între MHA și MQA. În loc să partajați un singur set de K/V între toate capete (MQA) sau să aveți unul pentru fiecare cap (MHA), GQA grupează capete în g grupuri, cu fiecare grup care împărtășește un set de K/V-uri. Cu g = 1 obținem MQA, cu g = h obținem MHA.
Esempio: 8 query heads, 2 KV groups (g=2)
Gruppo 1: Q1, Q2, Q3, Q4 condividono K1, V1
Gruppo 2: Q5, Q6, Q7, Q8 condividono K2, V2
KV Cache: g * d_k * seq_len * 2 = 2 * 128 * 4096 * 2 = 2.1 MB
(16x meno di MHA, ma solo 2x più di MQA)
Modelli che usano GQA:
- Llama 2 (70B): 8 KV heads, 64 query heads
- Llama 3: GQA con rapporto 8:1
- Mistral 7B: 8 KV heads, 32 query heads
Comparația variantelor de atenție
| Variantă | Capete KV | Memoria cache KV | calitate | Modele |
|---|---|---|---|---|
| MHA | h (toate) | Maxim | Îmbunătăţi | BERT, GPT-2, GPT-3 |
| GQA | g (grupuri) | reducerea h/d | Aproape egal cu MHA | Lama 2/3, Mistral |
| MQA | 1 | Minim | Ușoară scădere | PaLM, Falcon |
10.4 Fereastra glisantă Atenție
La Atenție fereastră glisantă, folosit în Mistral și Longformer, limite atenție la o fereastră locală de w jetoane pentru fiecare poziție. În loc să calculeze atenția asupra întregii secvențe (O(n^2)), fiecare jeton vede doar jetonele w anterioare, reducând complexitatea la O(n * w).
Sequenza: t1 t2 t3 t4 t5 t6 t7 t8
Attention di t5 (window=3): vede solo [t3, t4, t5]
Attention di t8 (window=3): vede solo [t6, t7, t8]
Attention Matrix (1 = visibile, 0 = mascherato):
t1 t2 t3 t4 t5 t6 t7 t8
t1 [ 1 0 0 0 0 0 0 0 ]
t2 [ 1 1 0 0 0 0 0 0 ]
t3 [ 1 1 1 0 0 0 0 0 ]
t4 [ 0 1 1 1 0 0 0 0 ]
t5 [ 0 0 1 1 1 0 0 0 ]
t6 [ 0 0 0 1 1 1 0 0 ]
t7 [ 0 0 0 0 1 1 1 0 ]
t8 [ 0 0 0 0 0 1 1 1 ]
L'informazione NON si perde: attraverso più layer stacked,
l'informazione di t1 può raggiungere t8 per propagazione.
Con L layer e window w, la reception field effettiva e L * w.
10.5 Sună Atenție și Atenție Paged
Pentru contexte foarte lungi (peste 1 milion de jetoane), au apărut noi inovații:
- Sună Atenție: distribuie calculul atenției pe mai multe GPU-uri organizat într-un inel. Fiecare GPU calculează atenția pe un segment al secvenței și transmite rezultatele următoarei GPU. RingX (2025) atinge o eficiență de 94%. până la 4096 GPU-uri cu 1 milion de secvențe de token.
- PagedAtenție: inspirat de managementul memoriei virtuale sisteme de operare, alocă memoria cache KV în blocuri (pagini) necontigue, eliminând fragmentarea memoriei. Este baza vLLM și permite dimensiuni de lot de până la de 76 de ori mai mare.
- FlexAttention (PyTorch): un API unificat care acceptă mai multe variante de atenție (GQA, cauzal, fereastră glisantă, PagedAttention) cu mai puțin de 5% cheltuieli generale comparativ cu implementările dedicate.
11. Aplicații: Arhitecturile transformatoarelor în practică
Arhitectura Transformer a dat naștere la trei familii principale de modele, fiecare care folosește atenția în mod diferit.
11.1 Numai codificator: BERT și derivate
Se folosesc modele numai cu codificator autoatenție bidirecțională: fiecare jeton pot vedea toate celelalte jetoane din secvență, atât cele anterioare, cât și cele cele ulterioare. Acest lucru le face ideale pentru sarcinile de înțelegere a limbii.
BERT (Reprezentări codificatoare bidirecționale de la transformatoare)
- Pre-antrenament: Model de limbaj mascat (MLM) + Predicția următoarei propoziții
- Atenţie: Autoatenție bidirecțională (vede întreaga secvență)
- Sarcini: Clasificare, recunoaștere a entității numite, răspuns la întrebări
- Variante: Roberta, ALBERT, DeBERTa, DistilBERT
11.2 Numai decodor: GPT și familia LLM
Se folosesc numai modele cu decodor auto-atentie mascata (cauzala): fiecare jeton vede doar jetoanele anterioare. Sunt optimizate pentru generarea de text autoregresiv.
Modele numai cu decodor
| Model | Parametrii | Varianta de atentie | Fereastra de context |
|---|---|---|---|
| GPT-3 | 175B | MHA standard | Jetoane 2K-4K |
| GPT-4 | ~1,8 T (MoE) | GQA (estimat) | 128K jetoane |
| Lama 3 405B | 405B | GQA + frânghie | 128K jetoane |
| Mistral 7B | 7.3B | GQA + fereastră glisantă | 32K jetoane |
| Claude (antropic) | Nepublicat | Nepublicat | 200.000 de jetoane |
11.3 Encoder-Decoder: Modele T5 și Seq2Seq
Modelele de codificator-decodor folosesc toate cele trei tipuri de atenție: autoatenție bidirecțional în codificator, auto-atentie mascata in decodor e atenție încrucișată între decodor și codificator. Sunt ideale pentru sarcini care ele transformă o intrare într-o ieșire (traducere, rezumat, răspuns la întrebări).
Modele Encoder-Decoder
- T5: „Transformator de transfer text în text” - fiecare sarcină este formulată ca text-în-text-out
- BART: Dezgomot autoencoder pentru generare și înțelegere
- mBART: BART multilingv pentru traducere
- Flan-T5: T5 instruit cu reglarea instrucțiunilor
11.4 Vision Transformer (ViT)
Atenția nu se limitează la text. THE Transformator de vedere aplica atenție personală la imagini, împărțirea imaginii în patch-uri (de exemplu, 16x16 pixeli) e tratând fiecare plasture ca pe un „jeton”. Acest lucru a demonstrat că atenția este a mecanism general aplicabil oricărui tip de date secvenţiale.
Immagine 224x224 pixel
|
v
Dividi in patch 16x16: (224/16)^2 = 196 patch
|
v
Ogni patch -> flatten -> proiezione lineare -> patch embedding
|
v
[CLS] + 196 patch embeddings + positional encoding
|
v
Transformer Encoder (self-attention su 197 token)
|
v
[CLS] token -> classificazione dell'immagine
Concluzii și pașii următori
În acest articol am acoperit întregul arc al mecanismului atenției: de la problemă a dependențelor pe termen lung în RNN-uri, la intuiția Query-Key-Value, la formula de la Scaled Dot-Product Atention, la Multi-Head Attention, până la arhitectură Transformator complet. Am implementat autoatenția de la zero în PyTorch e a explorat variațiile moderne care fac posibile modele cu milioane de jetoane de context.
Atenția este cărămida fundamentală pe care se construiește toată învățarea profundă modernă. Înțelegerea modului în care funcționează vă permite să înțelegeți de ce funcționează unele optimizări, de ce anumite modele sunt mai rapide decât altele și cum să alegeți arhitectura potrivită pentru cazul dvs. de utilizare.
Concepte cheie de reținut
- Atenţie permite conexiuni directe între orice pereche de jetoane, fără blocaje
- Scalare (sqrt(d_k)) previne gradienții instabili în softmax
- Multi-capete captați diferite relații în paralel, fără costuri suplimentare
- Auto-atenție creează reprezentări contextuale; Atenție încrucișată conectați codificatorul și decodorul
- Codificare pozițională furnizează informații despre comandă (sinusoidal, învățat, RoPE)
- Flash Atenție optimizați implementarea hardware fără a schimba matematica
- GQA și compromisul optim între calitate (MHA) și eficiență (MQA)
În articolul urmator ale seriei, vom explora reglaj fin de transformatoare cu LoRA, QLoRA și adaptoare: Cum se potrivesc modele pre-antrenate la sarcini specifice prin modificarea doar o mică parte a parametrilor, reducând GPU și memoria costă drastic.
Resurse suplimentare
- Hârtie originală: „Atenția este tot ce ai nevoie” (Vaswani și colab., 2017)
- Flash Atenție 3: „Atenție rapidă și precisă cu asincronie și precizie scăzută” (Dao și colab., 2024)
- Documente GQA: „GQA: Training Generalized Multi-Query Transformer Models” (Ainslie și colab., 2023)
- Transformerul ilustrat: Ghid vizual de Jay Alammar
- Documentația PyTorch: torch.nn.MultiheadAtenție pentru implementări optimizate
- Fața îmbrățișată: Documentația transformatoarelor cu exemple practice







