01 - Aandachtsmechanisme in transformatoren: complete gids
In 2017 veranderde een Google Brain-paper met de titel ‘Attention Is All You Need’ de zaken voor altijd het gebied van diep leren. De auteurs, Vaswani en collega's, stelden een architectuur voor volledig gebaseerd op een mechanisme genaamd aandacht, het elimineren van netwerken terugkerende (RNN) en convolutionele systemen die tot dan toe domineerden. Het resultaat was de architectuur Transformatoren, vandaag aan de basis van GPT-4, Claude, Llama 3, BERT, T5, Vision Transformers en vrijwel elk grensmodel.
Het begrijpen van het aandachtsmechanisme is geen academische oefening: het is de basis waarop het berust ze bouwen technieken als LoRA-verfijning, kwantisering, snoeien en inzetten op edge-apparaten, allemaal onderwerpen die we in deze serie zullen behandelen. Zonder begrip Als we een goed begrip hebben van hoe aandacht werkt, blijft elke volgende optimalisatie een black box.
In dit eerste artikel van de serie Geavanceerde deep learning en edge-implementatie, we zullen de aandacht diepgaand onderzoeken: van de aanvankelijke intuïtie tot de wiskundige formule, van implementatie in PyTorch tot moderne varianten zoals Flash Attention 3 en Grouped-Query Aandacht.
Serieoverzicht
| # | Item | Focus |
|---|---|---|
| 1 | Je bent hier - Aandachtsmechanisme in Transformers | Zelfaandacht, meerkoppige, complete architectuur |
| 2 | Fijnafstemming met LoRA, QLoRA en adapters | Parameter-efficiënte fijnafstemming |
| 3 | Kwantisering van modellen | INT8, INT4, GPTQ, AWQ |
| 4 | Snoeien en compressie | Parameterreductie, destillatie |
| 5 | Distillatie van kennis | Docent-student, kennisoverdracht |
| 6 | Ollama en LLM Lokaal | Lokale gevolgtrekking, optimalisatie |
| 7 | Visie Transformator | ViT, DINO, beeldclassificatie |
| 8 | Edge-implementatie | ONNX, TensorRT, mobiele apparaten |
| 9 | NAS en AutoML | Neurale architectuur zoeken |
| 10 | Benchmarking en optimalisatie | Profilering, statistieken, afstemming |
Wat je gaat leren
- omdat RNN's en LSTM's niet voldoende waren voor lange sequenties
- De intuïtie achter het aandachtsmechanisme: Query, Key en Value
- De complete formule van Scaled Dot-Product Attention
- Hoe Multi-Head Aandacht werkt en waarom je meerdere koppen nodig hebt
- Het verschil tussen zelfaandacht en kruisaandacht
- Hoe positionele codering het orderprobleem oplost
- De complete Transformer-architectuur: encoder en decoder
- Praktische implementatie in PyTorch, regel voor regel
- Moderne varianten: Flash Attention 3, GQA, Sliding Window Attention
- De echte architecturen: GPT (alleen decoder), BERT (alleen encoder), T5 (encoder-decoder)
1. Het volgordeprobleem: vóór de aandacht
Om te begrijpen waarom aandacht een revolutie was, moeten we uitgaan van de modellen die dat voor ogen hebben zij gingen haar voor. Deep learning voor sequenties (tekst, audio, tijdreeksen) was dat wel gedomineerd door twee architecturen: de RNN (terugkerende neurale netwerken) en de LSTM (langetermijngeheugen).
1.1 RNN's en het opeenvolgende knelpunt
RNN's verwerken reeksen één token tegelijk, waarbij ze een verborgen status doorgeven van de ene tijdstap naar de andere. Elk token werkt de verborgen status bij, zoals het fungeert "geheugen" van de reeks die tot nu toe is gezien.
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.
Dit is het probleem van afhankelijkheden op lange termijn. In een zin als ‘De kat, die drie jaar geleden uit het asiel was geadopteerd en nog leefde gelukkig met familie, hij lag te slapen op de bank", moet de RNN "kat" verbinden een "sliep" door tientallen tussenliggende tokens. De verborgen staat, gecomprimeerd in een vector van een vaste grootte, gaat onvermijdelijk oudere informatie verloren.
1.2 LSTM: een verbetering, geen oplossing
LSTM's introduceerden een poortmechanisme (invoerpoort, vergeetpoort, uitvoerpoort) voor bepalen welke informatie moet worden bewaard en welke moet worden weggegooid. Dit verbeterde de situatie, maar het loste het niet op. LSTM’s kampen nog steeds met twee fundamentele problemen:
Beperkingen van RNN's/LSTM's
| Probleem | Beschrijving | Invloed |
|---|---|---|
| Volgorde | Elk token is afhankelijk van het vorige: het kan niet worden geparallelliseerd | Zeer langzame training op lange reeksen |
| Knelpunt | Alle informatie loopt via één vervoerder | Informatieverlies met reeksen > 100-200 tokens |
| Verloop verdwijnt | Gradiënten krimpen exponentieel tijdens backpropagation | Het model slaagt er niet in verre relaties te leren |
We hadden een mechanisme nodig waarmee elk token toegang kreeg direct een elk ander token in de reeks, zonder tussenliggende toestanden te hoeven doorlopen. Dit mechanisme en deaandacht.
2. Wat is aandacht: intuïtie
Aandacht is een mechanisme dat een model in staat stelt dit te doen concentreer je eigen wees voorzichtig op de meest relevante delen van de input bij het genereren van de output. In plaats daarvan om de hele reeks in één enkele vector te comprimeren, creëert aandacht een verbinding direct tussen elke uitvoerpositie en alle invoerposities.
Analogie: zoeken in een bibliotheek
Stel je voor dat je in een boekwinkel bent en op zoek bent naar informatie over de "geschiedenis van Transformers". Je hebt er één in gedachten verzoek (Vragen). Elk boek heeft een titel (Sleutel) die de inhoud ervan beschrijft. Wanneer de titel overeenkomt met uw vraag, extraheer de inhoud (Waarde) van dat boek. Aandacht werkt precies zo:
- Vraag (V): "Wat zoek ik?" - de vraag die het huidige token stelt
- Sleutel (K): "Wat zit er in dit artikel?" - het label van elk token in de reeks
- Waarde (V): “Hier is de informatie” – de daadwerkelijke inhoud van elk token
Het mechanisme berekent a compatibiliteitsscore tussen de query en elke sleutel. Deze score bepaalt hoeveel aandacht er wordt besteed aan de bijbehorende Waarde. De scores komen genormaliseerd via softmax om gewichten te verkrijgen die optellen tot 1, en het eindresultaat is één gewogen gemiddelde van de waarden.
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. Scaled Dot-product Let op: de formule
De wiskundige formulering van aandacht die wordt gebruikt in Transformers en de Geschaald Dot-productaandacht. Het is elegant in zijn eenvoud en computationeel efficiënt dankzij het gebruik van matrixbewerkingen.
De aandachtsformule
Aandacht(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V
Waar:
- Q (Query): matrix van grootte (n x d_k), waarbij n het aantal tokens is en d_k de grootte van de queries/sleutel is
- K (Sleutel): array van dimensies (n x d_k)
- V (Waarde): matrix van dimensie (n x d_v), waarbij d_v de dimensie van de waarden is
- d_k: sleutelgrootte, gebruikt als schaalfactor
- V * K^T: scalair product tussen query en sleutel (n x n scorematrix)
- / sqrt(d_k): schaalfactor om hellingen te stabiliseren
- zachtmax: normaliseert de scores in gewichten die opgeteld 1 zijn
3.1 Waarom schalen noodzakelijk is
Zonder de factor sqrt(d_k), levert het puntproduct tussen Q en K toenemende waarden op
proportioneel met de afmeting d_k. Met d_k = 512 kunnen de scalaire producten bereiken
zeer grote waarden. Wanneer deze waarden in de softmax terechtkomen, produceren ze verdelingen
bijna één heet (één gewicht bijna 1, alle andere bijna 0), met extreem kleine hellingen.
Schalen voorkomt dit probleem.
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 Stap voor stap: berekening van aandacht
Laten we een concreet numeriek voorbeeld bekijken met een reeks van 3 tokens en 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]
Aandacht voor complexiteit
De matrix Q * K^T heeft dimensie n x n, waarbij n de lengte van de reeks is. Met n = 1000 heeft de matrix 1.000.000 elementen. Met n = 100.000 heeft het 10 miljard elementen. Deze O(n^2) kwadratische complexiteit is het belangrijkste knelpunt van Transformers en de reden waarom varianten als Flash Attention en Sliding Window Attention zijn ontwikkeld.
4. Aandacht voor meerdere hoofden: kijk vanuit meerdere hoeken
Met één enkele aandachtsbewerking wordt één type relatie tussen tokens vastgelegd. Maar relaties in een reeks zijn er meerdere: syntactische relaties (onderwerp-werkwoord), semantische (synoniemen, context), positionals (aangrenzende tokens) en vele anderen. Daar Multi-head aandacht lost dit probleem op door aandacht parallel uit te voeren met verschillende projecties.
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)
Met h = 8 hoofden en d_model = 512, elk hoofd werkt op één hoofd
dimensie ruimte d_k = d_v = 512 / 8 = 64. De totale rekenkosten
Het is vergelijkbaar met dat van een enkele attentie met volledige grootte, omdat de koppen werken
parallel op kleinere deelruimten.
Wat elk hoofd leert
Empirisch onderzoek heeft aangetoond dat verschillende hoofden zich specialiseren in verschillende patronen:
- Hoofd 1: Kan de relaties tussen onderwerp en werkwoord leren
- Hoofd 2: Kan coreferentierelaties leren (voornaamwoorden en hun antecedenten)
- Hoofd 3: Kan zich concentreren op aangrenzende tokens (lokale n-grammen)
- Hoofd 4: Het zou langeafstandsrelaties tussen zinnen kunnen vastleggen
- Andere hoofden: Syntactische patronen, entiteiten, discoursstructuur
Multi-head aandachtsformule
MultiHead(Q, K, V) = Concat(kop_1, ..., kop_h) * W_O
Waar hoofd_i = Aandacht(Q * W_Qi, K * W_Ki, V * W_Vi)
Typische parameters in het originele artikel: d_model = 512, h = 8, d_k = d_v = 64. In moderne modellen: d_model = 4096-8192, h = 32-128.
5. Zelfaandacht: een teken dat alle anderen in de gaten houdt
La zelf-aandacht en het specifieke geval waar Query, Key en Value vandaan komen allemaal uit dezelfde reeks. Elk token genereert zijn eigen query, sleutel en waarde en gebruikt de query om de sleutels van alle andere tokens (inclusief zichzelf) te "vragen".
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
Zelfaandacht is het hart van de Transformers. En waardoor het model kan worden gebouwd contextuele representaties: De representatie van elke token-insluiting informatie uit de hele reeks, gewogen op relevantie. Het woord "bank" zal hebben een andere weergave in "rivieroever" en "bankrekening" vanwege de omringende tokens beïnvloed de representatie ervan door middel van aandacht.
Gemaskeerde zelfaandacht in decoders
In generatieve modellen (decoders) is zelfaandacht e maskerade: elke token kan alleen eerdere tokens zien, geen toekomstige tokens. Dit wordt geïmplementeerd de scores van toekomstige tokens instellen op -infinity vóór de softmax, produceren gewichten gelijk aan nul. Dit is de causale aandacht gebruikt in GPT, Lama en alle autoregressieve modellen.
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. Cross-aandacht: wanneer encoders en decoders communiceren
La kruis-aandacht (of encoder-decoder aandacht) en het mechanisme dat dat doet Hiermee kan de decoder de uitvoer van de encoder "bekijken". In tegenstelling tot zelfaandacht, waar Q, K en V uit dezelfde reeks komen, komen de vragen door kruisaandacht van de decoder en de sleutel/waarde van de 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.
Kruisaandacht is fundamenteel in de architectuur encoder-decoder gebruikt voor automatische vertaling (T5, mBART), tekstsamenvatting en voorwaardelijke generatie. In T5 verwerkt bijvoorbeeld de encoder de ingevoerde tekst en genereert de decoder de tekst output, waarbij kruisaandacht wordt gebruikt om de encoder bij elke generatiestap te raadplegen.
De drie soorten aandacht bij Transformers
| Type | Q-bron | Bron K, V | Waar te gebruiken |
|---|---|---|---|
| Zelfaandacht (encoder) | Encoder-ingangen | Encoder-ingangen | BERT-encoder, T5-encoder |
| Gemaskeerde zelfaandacht | Ingangsdecoder | Ingangsdecoder | GPT, Lama, T5-decoder |
| Kruis aandacht | Decoders | Encoder-uitgang | T5-decoder, mBART |
7. Positionele codering: hoe transformatoren de volgorde kennen
In tegenstelling tot RNN's, die tokens in opeenvolgende volgorde verwerken, kan de zelfaandacht e onveranderlijk wat betreft orde: het resultaat verandert niet als u de invoertokens permuteert. "De kat eet de vis" en "de kat eet de vis" ze zouden dezelfde output produceren zonder een aanvullend mechanisme. De positioneel codering lost dit probleem op door informatie toe te voegen over de locatie van elk teken.
7.1 Sinusoïdale positionele codering (origineel artikel)
Het originele artikel gebruikt sinusoïdale functies om positionele coderingen te genereren:
Sinusoïdale positionele coderingsformules
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
Waar pos en de positie van het token in de reeks e i en de maat. Even posities gebruiken sinus, oneven posities gebruiken cosinus. De verschillende frequentie voor elk Dankzij de grootte kan het model relatieve positionele relaties leren.
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 Aangeleerde positionele codering
Een alternatief voor positionele sinusoïdale codering en het gebruik van geleerde inbedding (geleerd): een reeks trainbare parameters, één rij voor elke positie. Deze aanpak wordt gebruikt in BERT en GPT-2. Het voordeel is dat het model kan leren optimale positionele patronen voor de specifieke taak. Het nadeel is dat de lengte maximum van de reeks en vastgelegd tijdens de training.
Vergelijking van positionele codering
| Type | Voordelen | Nadelen | Gebruikt binnen |
|---|---|---|---|
| Sinusvormig | Geen extra parameters, generaliseert naar langere reeksen | Vaste patronen, niet geoptimaliseerd voor de taak | Originele transformator |
| Geleerd | Geoptimaliseerd voor de specifieke taak | Vaste maximale lengte, meerdere parameters | BERT, GPT-2 |
| RoPE (roterend) | Leg relatieve posities vast, uitbreidbaar | Grotere implementatiecomplexiteit | Lama, Mistral, GPT-NeoX |
| Alibi | Geen parameters, goede extrapolatie | Lineaire bias kan beperkend zijn | BLOEM, MPT |
8. De complete transformatorarchitectuur
Met alle puzzelstukjes in de hand kunnen we nu de Transformer-architectuur in elkaar zetten compleet. De originele Transformer bestaat uit een stapel-encoders en een stapeldecoder, elk opgebouwd uit N identieke lagen (N = 6 in het papier origineel).
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 Resterende verbindingen
Elke sublaag (aandacht of feed-forward) heeft een resterende verbinding:
de uitvoer van de sublaag wordt opgeteld bij de invoer. De formule e
output = LayerNorm(x + SubLayer(x)). Resterende verbindingen lossen het probleem op
verdwijnend gradiëntprobleem in diepe netwerken, waardoor gradiënten kunnen stromen
rechtstreeks via snelkoppelingen.
8.2 Feed-Forward-netwerk
Na aandacht gaat elk token door a feed-forward netwerk onafhankelijk van elkaar toegepast op elke positie. Het is samengesteld uit twee lineaire transformaties met een niet-lineaire activering (ReLU in het originele artikel, GELU of SwiGLU in de modellen moderne):
FFN(x) = W2 * activering(W1 * x + b1) + b2
De interne afmeting (d_ff) is doorgaans 4 keer d_model. Met d_model = 512, d_ff = 2048. In moderne modellen zoals Llama 3 gaat d_ff omhoog naar 14.336 met d_model = 4096.
8.3 Laagnormalisatie
La Normalisatie van lagen normaliseert activeringen langs de dimensie van de kenmerken (niet de batch). Stabiliseert training en versnelt convergentie. In de originele Transformer wordt Post-LN gebruikt (normalisatie na de restverbinding), maar de meeste moderne modellen gebruiken Pre-LN (normalisatie vóór de sublaag), wat stabieler is tijdens de training.
9. PyTorch-implementatie: zelfaandacht vanaf nul
Laten we van theorie naar code gaan. We implementeren Scaled Dot-Product Attention en Multi-Head Attention helemaal opnieuw in PyTorch, zonder gebruik te maken van vooraf gebouwde modules.
9.1 Aandacht voor geschaalde dot-producten
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 Aandacht voor meerdere hoofden
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 Voorbeeld van gebruik
# 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. Moderne variaties van aandacht
De O(n^2) kwadratische complexiteit van standaardaandacht motiveerde de ontwikkeling van talrijke geoptimaliseerde varianten. Deze variaties zijn van fundamenteel belang voor moderne modellen beheer van contexten van 100.000 tot meer dan 1 miljoen tokens.
10.1 Flash-aandacht (v1, v2, v3)
Flits aandacht, ontwikkeld door Tri Dao en collega's, verandert niets aan de wiskunde van aandacht, maar optimaliseert radicaal de implementatie ervan op hardwareniveau. Het idee sleutel en vermijd het materialiseren van de volledige n x n-matrix van aandachtsscores GPU-geheugen (HBM), waarbij in plaats daarvan één benadering wordt gebruikt betegeld wie werkt volledig in SRAM (snel on-chip geheugen).
Evolutie van flitsaandacht
| Versie | Jaar | Belangrijke innovatie | Prestatie |
|---|---|---|---|
| Flitswaarschuwing 1 | 2022 | Tegels + gefuseerde kernel, IO-bewustzijn | 2-4x versnelling versus standaard |
| Flitswaarschuwing 2 | 2023 | Verbeterd parallellisme, minder communicatie | 2x verder dan v1 |
| Flitswaarschuwing 3 | 2024 | Asynchronie op Hopper GPU, FP8, warp-specialisatie | Tot 740 TFLOPS (FP16) op H100, 1,2 PFLOPS met FP8 |
Flash Attention 3 maakt gebruik van de specifieke kenmerken van NVIDIA Hopper GPU's (H100/H200): asynchronie tussen Tensor Core en TMA (Tensor Memory Accelerator) om over elkaar heen te leggen berekening en gegevensoverdracht, specialisatie warp voor interleaving optimaal van matmul- en softmax-bewerkingen, b.v FP8-blokkwantisering met numerieke fouten die 2,6 keer lager zijn dan bij een naïeve FP8-implementatie. Flits Aandacht is nu geïntegreerd in PyTorch, Hugging Face Transformers, vLLM en TensorRT-LLM.
10.2 Aandacht voor meerdere zoekopdrachten (MQA)
Voorgesteld door Shazeer in 2019, de Aandacht voor meerdere zoekopdrachten drastisch vermindert het geheugen dat nodig is voor de KV-cache tijdens inferentie. In plaats van een aparte set te hebben van sleutel en waarde voor elk hoofd, MQA deelt a enkel set van K en V onder allemaal de hoofden, waarbij verschillende zoekopdrachten worden onderhouden.
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 Aandacht voor gegroepeerde zoekopdrachten (GQA)
GQA, geïntroduceerd door Ainslie et al. in 2023, en een compromis tussen MHA en MQA. In plaats van een enkele set K/V te delen tussen alle heads (MQA) of er één voor elk te hebben hoofd (MHA), GQA-groepen gaan naar binnen g groepen, met elke groep dat deelt een set K/V's. Met g = 1 verkrijgen we MQA, met g = h verkrijgen we 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
Vergelijking van Attention-varianten
| Variant | KV-hoofden | KV-cachegeheugen | kwaliteit | Modellen |
|---|---|---|---|---|
| MHA | h (alle) | Maximaal | Verbeteren | BERT, GPT-2, GPT-3 |
| GQA | g (groepen) | h/d-reductie | Bijna gelijk aan MHA | Lama 2/3, Mistral |
| MQA | 1 | Minimaal | Lichte daling | PaLM, Valk |
10.4 Schuifraam Let op
La Schuifraam Let op, gebruikt in Mistral en Longformer, limieten aandacht voor een lokaal venster met w-tokens voor elke positie. In plaats van berekenen de aandacht op de hele reeks (O(n^2)), elk token ziet alleen de vorige w-tokens, het reduceren van de complexiteit tot 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 Belaandacht en PagedAttention
Voor zeer lange contexten (meer dan 1 miljoen tokens) zijn er verdere innovaties ontstaan:
- Ring Aandacht: verdeelt de aandachtsberekening over meerdere GPU's georganiseerd in een ring. Elke GPU berekent de aandacht voor een segment van de reeks en geeft de resultaten door aan de volgende GPU. RingX (2025) behaalt een efficiëntie van 94% tot 4096 GPU's met 1 miljoen tokenreeksen.
- PagedLet op: geïnspireerd door virtueel geheugenbeheer besturingssystemen, wijst de KV-cache toe aan niet-aaneengesloten blokken (pagina's), waardoor fragmentatie van het geheugen. Het is de basis van vLLM en maakt batchgroottes tot 76 keer hoger.
- FlexAttentie (PyTorch): een uniforme API die meerdere ondersteunt varianten van aandacht (GQA, causaal, schuifvenster, PagedAttention) met minder dan 5% overhead vergeleken met specifieke implementaties.
11. Toepassingen: Transformer-architecturen in de praktijk
De Transformer-architectuur heeft elk aanleiding gegeven tot drie hoofdfamilies van modellen die de aandacht op een andere manier gebruikt.
11.1 Alleen voor encoders: BERT en derivaten
Encoder-only modellen gebruiken bidirectionele zelfaandacht: elk teken kan alle andere tokens in de reeks zien, zowel de vorige als die daaropvolgende. Dit maakt ze ideaal voor taken op het gebied van taalbegrip.
BERT (Bidirectionele Encoder-representaties van Transformers)
- Vooropleiding: Masked Language Model (MLM) + voorspelling van de volgende zin
- Aandacht: Bidirectionele zelfaandacht (ziet de hele reeks)
- Taken: Classificatie, herkenning van benoemde entiteiten, beantwoorden van vragen
- Varianten: RoBERTa, ALBERT, DeBERTa, DistillBERT
11.2 Alleen decoders: GPT en de LLM-familie
Modellen met alleen decoders gebruiken gemaskeerde zelfaandacht (causaal): elk teken ziet alleen eerdere tokens. Ze zijn geoptimaliseerd voor het genereren van autoregressieve tekst.
Modellen met alleen decoders
| Model | Parameters | Aandacht variant | Contextvenster |
|---|---|---|---|
| GPT-3 | 175B | Standaard MHA | 2K-4K-tokens |
| GPT-4 | ~1,8T (MoE) | GQA (geschat) | 128K-tokens |
| Lama 3 405B | 405B | GQA + RoPE | 128K-tokens |
| Mistral 7B | 7.3B | GQA + schuifraam | 32K-tokens |
| Claude (antropisch) | Niet gepubliceerd | Niet gepubliceerd | 200K-tokens |
11.3 Encoder-decoder: T5- en Seq2Seq-modellen
Encoder-decoder-modellen gebruiken alle drie soorten aandacht: zelf-aandacht bidirectioneel in de encoder, gemaskeerde zelfaandacht in de decoder e kruis-aandacht tussen decoder en encoder. Ze zijn ideaal voor taken die ze transformeren een input in een output (vertaling, samenvatting, antwoord op vragen).
Encoder-Decoder-modellen
- T5: "Text-to-Text Transfer Transformer" - elke taak is geformuleerd als tekst-in-tekst-uit
- BART: Ruisonderdrukkende auto-encoders voor generatie en begrip
- mBART: BART meertalig voor vertaling
- Vlaai-T5: T5 geïnstrueerd met instructieafstemming
11.4 Visietransformator (ViT)
De aandacht beperkt zich niet tot de tekst. DE Visie Transformator pas de zelfaandacht voor afbeeldingen, waarbij de afbeelding in stukken wordt verdeeld (bijvoorbeeld 16x16 pixels) e elke patch behandelen als een "token". Dit toonde aan dat aandacht een algemeen mechanisme dat van toepassing is op elk type sequentiële gegevens.
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
Conclusies en volgende stappen
In dit artikel hebben we de hele boog van het aandachtsmechanisme besproken: vanaf het probleem van afhankelijkheden op de lange termijn in RNN's, naar de intuïtie van Query-Key-Value, naar de formule van Scaled Dot-Product-aandacht, tot multi-head-aandacht, tot aan architectuur Volledige transformator. We hebben zelfaandacht helemaal opnieuw geïmplementeerd in PyTorch e onderzocht de moderne varianten die modellen met miljoenen tokens mogelijk maken van context.
Aandacht is de fundamentele steen waarop al het moderne deep learning is gebouwd. Als u begrijpt hoe het werkt, kunt u begrijpen waarom sommige optimalisaties werken. waarom bepaalde modellen sneller zijn dan andere en hoe je de juiste architectuur kiest voor uw gebruiksscenario.
Sleutelbegrippen om te onthouden
- Aandacht maakt directe verbindingen tussen elk paar tokens mogelijk, zonder knelpunten
- Schalen (sqrt(d_k)) voorkomt onstabiele gradiënten in softmax
- Meerkoppig leg verschillende relaties parallel vast zonder extra kosten
- Zelfaandacht creëert contextuele representaties; Kruis aandacht encoder en decoder aansluiten
- Positionele codering biedt orderinformatie (sinusoïdaal, aangeleerd, RoPE)
- Flits aandacht optimaliseer de hardware-implementatie zonder de wiskunde te veranderen
- GQA en het optimale compromis tussen kwaliteit (MHA) en efficiëntie (MQA)
In de volgend artikel van de serie, zullen we de fijnafstemming van transformatoren met LoRA, QLoRA en adapters: Hoe u vooraf getrainde modellen kunt passen aan specifieke taken door slechts een klein deel van de parameters te veranderen, waardoor ze worden verminderd GPU- en geheugenkosten zijn drastisch.
Aanvullende bronnen
- Origineel papier: "Aandacht is alles wat je nodig hebt" (Vaswani et al., 2017)
- Flitsaandacht 3: “Snelle en nauwkeurige aandacht met asynchronie en lage precisie” (Dao et al., 2024)
- GQA-papieren: “GQA: Training van gegeneraliseerde Multi-Query Transformer-modellen” (Ainslie et al., 2023)
- De geïllustreerde transformator: Visuele gids door Jay Alammar
- PyTorch-documentatie: torch.nn.MultiheadAttentie voor geoptimaliseerde implementaties
- Knuffelend gezicht: Transformatordocumentatie met praktijkvoorbeelden







