mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 14:40:37 +00:00
667 lines
28 KiB
Markdown
667 lines
28 KiB
Markdown
# 5. Architettura LLM
|
|
|
|
## Architettura LLM
|
|
|
|
{% hint style="success" %}
|
|
L'obiettivo di questa quinta fase è molto semplice: **Sviluppare l'architettura del LLM completo**. Metti tutto insieme, applica tutti i livelli e crea tutte le funzioni per generare testo o trasformare testo in ID e viceversa.
|
|
|
|
Questa architettura sarà utilizzata sia per l'addestramento che per la previsione del testo dopo che è stato addestrato.
|
|
{% endhint %}
|
|
|
|
Esempio di architettura LLM da [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01\_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01\_main-chapter-code/ch04.ipynb):
|
|
|
|
Una rappresentazione ad alto livello può essere osservata in:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (3) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31">https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31</a></p></figcaption></figure>
|
|
|
|
1. **Input (Testo Tokenizzato)**: Il processo inizia con testo tokenizzato, che viene convertito in rappresentazioni numeriche.
|
|
2. **Layer di Embedding dei Token e Layer di Embedding Posizionale**: Il testo tokenizzato passa attraverso un **layer di embedding dei token** e un **layer di embedding posizionale**, che cattura la posizione dei token in una sequenza, fondamentale per comprendere l'ordine delle parole.
|
|
3. **Blocchi Transformer**: Il modello contiene **12 blocchi transformer**, ciascuno con più livelli. Questi blocchi ripetono la seguente sequenza:
|
|
* **Attenzione Multi-Testa Mascherata**: Consente al modello di concentrarsi su diverse parti del testo di input contemporaneamente.
|
|
* **Normalizzazione del Livello**: Un passo di normalizzazione per stabilizzare e migliorare l'addestramento.
|
|
* **Layer Feed Forward**: Responsabile dell'elaborazione delle informazioni dal layer di attenzione e della formulazione di previsioni sul token successivo.
|
|
* **Layer di Dropout**: Questi layer prevengono l'overfitting eliminando casualmente unità durante l'addestramento.
|
|
4. **Layer di Output Finale**: Il modello produce un **tensore di dimensione 4x50,257**, dove **50,257** rappresenta la dimensione del vocabolario. Ogni riga in questo tensore corrisponde a un vettore che il modello utilizza per prevedere la prossima parola nella sequenza.
|
|
5. **Obiettivo**: L'obiettivo è prendere questi embedding e convertirli di nuovo in testo. In particolare, l'ultima riga dell'output viene utilizzata per generare la prossima parola, rappresentata come "avanti" in questo diagramma.
|
|
|
|
### Rappresentazione del Codice
|
|
```python
|
|
import torch
|
|
import torch.nn as nn
|
|
import tiktoken
|
|
|
|
class GELU(nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def forward(self, x):
|
|
return 0.5 * x * (1 + torch.tanh(
|
|
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
|
|
(x + 0.044715 * torch.pow(x, 3))
|
|
))
|
|
|
|
class FeedForward(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.layers = nn.Sequential(
|
|
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
|
|
GELU(),
|
|
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
|
|
)
|
|
|
|
def forward(self, x):
|
|
return self.layers(x)
|
|
|
|
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
|
|
|
|
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
|
|
|
|
class LayerNorm(nn.Module):
|
|
def __init__(self, emb_dim):
|
|
super().__init__()
|
|
self.eps = 1e-5
|
|
self.scale = nn.Parameter(torch.ones(emb_dim))
|
|
self.shift = nn.Parameter(torch.zeros(emb_dim))
|
|
|
|
def forward(self, x):
|
|
mean = x.mean(dim=-1, keepdim=True)
|
|
var = x.var(dim=-1, keepdim=True, unbiased=False)
|
|
norm_x = (x - mean) / torch.sqrt(var + self.eps)
|
|
return self.scale * norm_x + self.shift
|
|
|
|
class TransformerBlock(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.att = MultiHeadAttention(
|
|
d_in=cfg["emb_dim"],
|
|
d_out=cfg["emb_dim"],
|
|
context_length=cfg["context_length"],
|
|
num_heads=cfg["n_heads"],
|
|
dropout=cfg["drop_rate"],
|
|
qkv_bias=cfg["qkv_bias"])
|
|
self.ff = FeedForward(cfg)
|
|
self.norm1 = LayerNorm(cfg["emb_dim"])
|
|
self.norm2 = LayerNorm(cfg["emb_dim"])
|
|
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
|
|
|
|
def forward(self, x):
|
|
# Shortcut connection for attention block
|
|
shortcut = x
|
|
x = self.norm1(x)
|
|
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
|
|
x = self.drop_shortcut(x)
|
|
x = x + shortcut # Add the original input back
|
|
|
|
# Shortcut connection for feed forward block
|
|
shortcut = x
|
|
x = self.norm2(x)
|
|
x = self.ff(x)
|
|
x = self.drop_shortcut(x)
|
|
x = x + shortcut # Add the original input back
|
|
|
|
return x
|
|
|
|
|
|
class GPTModel(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
|
|
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
|
|
self.drop_emb = nn.Dropout(cfg["drop_rate"])
|
|
|
|
self.trf_blocks = nn.Sequential(
|
|
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
|
|
|
|
self.final_norm = LayerNorm(cfg["emb_dim"])
|
|
self.out_head = nn.Linear(
|
|
cfg["emb_dim"], cfg["vocab_size"], bias=False
|
|
)
|
|
|
|
def forward(self, in_idx):
|
|
batch_size, seq_len = in_idx.shape
|
|
tok_embeds = self.tok_emb(in_idx)
|
|
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
|
|
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
|
|
x = self.drop_emb(x)
|
|
x = self.trf_blocks(x)
|
|
x = self.final_norm(x)
|
|
logits = self.out_head(x)
|
|
return logits
|
|
|
|
GPT_CONFIG_124M = {
|
|
"vocab_size": 50257, # Vocabulary size
|
|
"context_length": 1024, # Context length
|
|
"emb_dim": 768, # Embedding dimension
|
|
"n_heads": 12, # Number of attention heads
|
|
"n_layers": 12, # Number of layers
|
|
"drop_rate": 0.1, # Dropout rate
|
|
"qkv_bias": False # Query-Key-Value bias
|
|
}
|
|
|
|
torch.manual_seed(123)
|
|
model = GPTModel(GPT_CONFIG_124M)
|
|
out = model(batch)
|
|
print("Input batch:\n", batch)
|
|
print("\nOutput shape:", out.shape)
|
|
print(out)
|
|
```
|
|
### **Funzione di Attivazione GELU**
|
|
```python
|
|
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
|
class GELU(nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def forward(self, x):
|
|
return 0.5 * x * (1 + torch.tanh(
|
|
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
|
|
(x + 0.044715 * torch.pow(x, 3))
|
|
))
|
|
```
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **GELU (Gaussian Error Linear Unit):** Una funzione di attivazione che introduce non linearità nel modello.
|
|
* **Attivazione Liscia:** A differenza di ReLU, che annulla gli input negativi, GELU mappa dolcemente gli input sugli output, consentendo piccoli valori non nulli per gli input negativi.
|
|
* **Definizione Matematica:**
|
|
|
|
<figure><img src="../../.gitbook/assets/image (2) (1).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
{% hint style="info" %}
|
|
L'obiettivo dell'uso di questa funzione dopo i livelli lineari all'interno del livello FeedForward è cambiare i dati lineari in non lineari per consentire al modello di apprendere relazioni complesse e non lineari.
|
|
{% endhint %}
|
|
|
|
### **Rete Neurale FeedForward**
|
|
|
|
_I formati sono stati aggiunti come commenti per comprendere meglio le forme delle matrici:_
|
|
```python
|
|
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
|
class FeedForward(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.layers = nn.Sequential(
|
|
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
|
|
GELU(),
|
|
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
|
|
)
|
|
|
|
def forward(self, x):
|
|
# x shape: (batch_size, seq_len, emb_dim)
|
|
|
|
x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)
|
|
x = self.layers[1](x) # x shape remains: (batch_size, seq_len, 4 * emb_dim)
|
|
x = self.layers[2](x) # x shape: (batch_size, seq_len, emb_dim)
|
|
return x # Output shape: (batch_size, seq_len, emb_dim)
|
|
```
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **Rete FeedForward per Posizione:** Applica una rete completamente connessa a due strati a ciascuna posizione separatamente e in modo identico.
|
|
* **Dettagli dello Strato:**
|
|
* **Primo Strato Lineare:** Espande la dimensionalità da `emb_dim` a `4 * emb_dim`.
|
|
* **Attivazione GELU:** Applica non-linearità.
|
|
* **Secondo Strato Lineare:** Riduce la dimensionalità di nuovo a `emb_dim`.
|
|
|
|
{% hint style="info" %}
|
|
Come puoi vedere, la rete Feed Forward utilizza 3 strati. Il primo è uno strato lineare che moltiplicherà le dimensioni per 4 utilizzando pesi lineari (parametri da addestrare all'interno del modello). Poi, la funzione GELU è utilizzata in tutte quelle dimensioni per applicare variazioni non lineari per catturare rappresentazioni più ricche e infine un altro strato lineare è utilizzato per tornare alla dimensione originale.
|
|
{% endhint %}
|
|
|
|
### **Meccanismo di Attenzione Multi-Testa**
|
|
|
|
Questo è già stato spiegato in una sezione precedente.
|
|
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **Auto-Attenzione Multi-Testa:** Consente al modello di concentrarsi su diverse posizioni all'interno della sequenza di input durante la codifica di un token.
|
|
* **Componenti Chiave:**
|
|
* **Query, Chiavi, Valori:** Proiezioni lineari dell'input, utilizzate per calcolare i punteggi di attenzione.
|
|
* **Teste:** Molteplici meccanismi di attenzione che funzionano in parallelo (`num_heads`), ciascuno con una dimensione ridotta (`head_dim`).
|
|
* **Punteggi di Attenzione:** Calcolati come il prodotto scalare di query e chiavi, scalati e mascherati.
|
|
* **Mascheramento:** Una maschera causale è applicata per impedire al modello di prestare attenzione ai token futuri (importante per modelli autoregressivi come GPT).
|
|
* **Pesi di Attenzione:** Softmax dei punteggi di attenzione mascherati e scalati.
|
|
* **Vettore di Contesto:** Somma pesata dei valori, secondo i pesi di attenzione.
|
|
* **Proiezione di Uscita:** Strato lineare per combinare le uscite di tutte le teste.
|
|
|
|
{% hint style="info" %}
|
|
L'obiettivo di questa rete è trovare le relazioni tra i token nello stesso contesto. Inoltre, i token sono divisi in diverse teste per prevenire l'overfitting, anche se le relazioni finali trovate per testa sono combinate alla fine di questa rete.
|
|
|
|
Inoltre, durante l'addestramento viene applicata una **maschera causale** affinché i token successivi non vengano presi in considerazione quando si cercano le relazioni specifiche a un token e viene applicato anche un **dropout** per **prevenire l'overfitting**.
|
|
{% endhint %}
|
|
|
|
### **Normalizzazione** dello Strato
|
|
```python
|
|
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
|
class LayerNorm(nn.Module):
|
|
def __init__(self, emb_dim):
|
|
super().__init__()
|
|
self.eps = 1e-5 # Prevent division by zero during normalization.
|
|
self.scale = nn.Parameter(torch.ones(emb_dim))
|
|
self.shift = nn.Parameter(torch.zeros(emb_dim))
|
|
|
|
def forward(self, x):
|
|
mean = x.mean(dim=-1, keepdim=True)
|
|
var = x.var(dim=-1, keepdim=True, unbiased=False)
|
|
norm_x = (x - mean) / torch.sqrt(var + self.eps)
|
|
return self.scale * norm_x + self.shift
|
|
```
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **Layer Normalization:** Una tecnica utilizzata per normalizzare gli input attraverso le caratteristiche (dimensioni di embedding) per ciascun esempio individuale in un batch.
|
|
* **Componenti:**
|
|
* **`eps`:** Una piccola costante (`1e-5`) aggiunta alla varianza per prevenire la divisione per zero durante la normalizzazione.
|
|
* **`scale` e `shift`:** Parametri apprendibili (`nn.Parameter`) che consentono al modello di scalare e spostare l'output normalizzato. Sono inizializzati rispettivamente a uno e zero.
|
|
* **Processo di Normalizzazione:**
|
|
* **Calcola Media (`mean`):** Calcola la media dell'input `x` attraverso la dimensione di embedding (`dim=-1`), mantenendo la dimensione per il broadcasting (`keepdim=True`).
|
|
* **Calcola Varianza (`var`):** Calcola la varianza di `x` attraverso la dimensione di embedding, mantenendo anche la dimensione. Il parametro `unbiased=False` garantisce che la varianza venga calcolata utilizzando l'estimatore biased (dividendo per `N` invece di `N-1`), il che è appropriato quando si normalizza sulle caratteristiche piuttosto che sui campioni.
|
|
* **Normalizza (`norm_x`):** Sottrae la media da `x` e divide per la radice quadrata della varianza più `eps`.
|
|
* **Scala e Sposta:** Applica i parametri apprendibili `scale` e `shift` all'output normalizzato.
|
|
|
|
{% hint style="info" %}
|
|
L'obiettivo è garantire una media di 0 con una varianza di 1 attraverso tutte le dimensioni dello stesso token. L'obiettivo di questo è **stabilizzare l'addestramento delle reti neurali profonde** riducendo lo shift covariato interno, che si riferisce al cambiamento nella distribuzione delle attivazioni della rete a causa dell'aggiornamento dei parametri durante l'addestramento.
|
|
{% endhint %}
|
|
|
|
### **Blocco Transformer**
|
|
|
|
_Sono state aggiunte forme come commenti per comprendere meglio le forme delle matrici:_
|
|
```python
|
|
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
|
|
|
class TransformerBlock(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.att = MultiHeadAttention(
|
|
d_in=cfg["emb_dim"],
|
|
d_out=cfg["emb_dim"],
|
|
context_length=cfg["context_length"],
|
|
num_heads=cfg["n_heads"],
|
|
dropout=cfg["drop_rate"],
|
|
qkv_bias=cfg["qkv_bias"]
|
|
)
|
|
self.ff = FeedForward(cfg)
|
|
self.norm1 = LayerNorm(cfg["emb_dim"])
|
|
self.norm2 = LayerNorm(cfg["emb_dim"])
|
|
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
|
|
|
|
def forward(self, x):
|
|
# x shape: (batch_size, seq_len, emb_dim)
|
|
|
|
# Shortcut connection for attention block
|
|
shortcut = x # shape: (batch_size, seq_len, emb_dim)
|
|
x = self.norm1(x) # shape remains (batch_size, seq_len, emb_dim)
|
|
x = self.att(x) # shape: (batch_size, seq_len, emb_dim)
|
|
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
|
|
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
|
|
|
|
# Shortcut connection for feedforward block
|
|
shortcut = x # shape: (batch_size, seq_len, emb_dim)
|
|
x = self.norm2(x) # shape remains (batch_size, seq_len, emb_dim)
|
|
x = self.ff(x) # shape: (batch_size, seq_len, emb_dim)
|
|
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
|
|
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
|
|
|
|
return x # Output shape: (batch_size, seq_len, emb_dim)
|
|
|
|
```
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **Composizione dei Livelli:** Combina attenzione multi-testa, rete feedforward, normalizzazione dei livelli e connessioni residue.
|
|
* **Normalizzazione dei Livelli:** Applicata prima dei livelli di attenzione e feedforward per un addestramento stabile.
|
|
* **Connessioni Residue (Scorciatoie):** Aggiungono l'input di un livello alla sua uscita per migliorare il flusso del gradiente e abilitare l'addestramento di reti profonde.
|
|
* **Dropout:** Applicato dopo i livelli di attenzione e feedforward per la regolarizzazione.
|
|
|
|
#### **Funzionalità Passo-Passo**
|
|
|
|
1. **Primo Percorso Residuo (Auto-Attenzione):**
|
|
* **Input (`shortcut`):** Salva l'input originale per la connessione residua.
|
|
* **Layer Norm (`norm1`):** Normalizza l'input.
|
|
* **Attenzione Multi-Testa (`att`):** Applica auto-attenzione.
|
|
* **Dropout (`drop_shortcut`):** Applica dropout per la regolarizzazione.
|
|
* **Aggiungi Residuo (`x + shortcut`):** Combina con l'input originale.
|
|
2. **Secondo Percorso Residuo (FeedForward):**
|
|
* **Input (`shortcut`):** Salva l'input aggiornato per la prossima connessione residua.
|
|
* **Layer Norm (`norm2`):** Normalizza l'input.
|
|
* **Rete FeedForward (`ff`):** Applica la trasformazione feedforward.
|
|
* **Dropout (`drop_shortcut`):** Applica dropout.
|
|
* **Aggiungi Residuo (`x + shortcut`):** Combina con l'input dal primo percorso residuo.
|
|
|
|
{% hint style="info" %}
|
|
Il blocco transformer raggruppa tutte le reti insieme e applica alcune **normalizzazioni** e **dropout** per migliorare la stabilità e i risultati dell'addestramento.\
|
|
Nota come i dropout siano effettuati dopo l'uso di ciascuna rete mentre la normalizzazione è applicata prima.
|
|
|
|
Inoltre, utilizza anche scorciatoie che consistono nell'**aggiungere l'uscita di una rete con il suo input**. Questo aiuta a prevenire il problema del gradiente che svanisce assicurando che i livelli iniziali contribuiscano "tanto" quanto quelli finali.
|
|
{% endhint %}
|
|
|
|
### **GPTModel**
|
|
|
|
_Le forme sono state aggiunte come commenti per comprendere meglio le forme delle matrici:_
|
|
```python
|
|
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
|
class GPTModel(nn.Module):
|
|
def __init__(self, cfg):
|
|
super().__init__()
|
|
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
|
|
# shape: (vocab_size, emb_dim)
|
|
|
|
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
|
|
# shape: (context_length, emb_dim)
|
|
|
|
self.drop_emb = nn.Dropout(cfg["drop_rate"])
|
|
|
|
self.trf_blocks = nn.Sequential(
|
|
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
|
|
)
|
|
# Stack of TransformerBlocks
|
|
|
|
self.final_norm = LayerNorm(cfg["emb_dim"])
|
|
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
|
|
# shape: (emb_dim, vocab_size)
|
|
|
|
def forward(self, in_idx):
|
|
# in_idx shape: (batch_size, seq_len)
|
|
batch_size, seq_len = in_idx.shape
|
|
|
|
# Token embeddings
|
|
tok_embeds = self.tok_emb(in_idx)
|
|
# shape: (batch_size, seq_len, emb_dim)
|
|
|
|
# Positional embeddings
|
|
pos_indices = torch.arange(seq_len, device=in_idx.device)
|
|
# shape: (seq_len,)
|
|
pos_embeds = self.pos_emb(pos_indices)
|
|
# shape: (seq_len, emb_dim)
|
|
|
|
# Add token and positional embeddings
|
|
x = tok_embeds + pos_embeds # Broadcasting over batch dimension
|
|
# x shape: (batch_size, seq_len, emb_dim)
|
|
|
|
x = self.drop_emb(x) # Dropout applied
|
|
# x shape remains: (batch_size, seq_len, emb_dim)
|
|
|
|
x = self.trf_blocks(x) # Pass through Transformer blocks
|
|
# x shape remains: (batch_size, seq_len, emb_dim)
|
|
|
|
x = self.final_norm(x) # Final LayerNorm
|
|
# x shape remains: (batch_size, seq_len, emb_dim)
|
|
|
|
logits = self.out_head(x) # Project to vocabulary size
|
|
# logits shape: (batch_size, seq_len, vocab_size)
|
|
|
|
return logits # Output shape: (batch_size, seq_len, vocab_size)
|
|
```
|
|
#### **Scopo e Funzionalità**
|
|
|
|
* **Strati di Embedding:**
|
|
* **Token Embeddings (`tok_emb`):** Converte gli indici dei token in embedding. Come promemoria, questi sono i pesi dati a ciascuna dimensione di ciascun token nel vocabolario.
|
|
* **Positional Embeddings (`pos_emb`):** Aggiunge informazioni posizionali agli embedding per catturare l'ordine dei token. Come promemoria, questi sono i pesi dati ai token in base alla loro posizione nel testo.
|
|
* **Dropout (`drop_emb`):** Applicato agli embedding per la regolarizzazione.
|
|
* **Blocchi Transformer (`trf_blocks`):** Stack di `n_layers` blocchi transformer per elaborare gli embedding.
|
|
* **Normalizzazione Finale (`final_norm`):** Normalizzazione dello strato prima dello strato di output.
|
|
* **Strato di Output (`out_head`):** Proietta gli stati nascosti finali alla dimensione del vocabolario per produrre logit per la previsione.
|
|
|
|
{% hint style="info" %}
|
|
L'obiettivo di questa classe è utilizzare tutte le altre reti menzionate per **prevedere il prossimo token in una sequenza**, fondamentale per compiti come la generazione di testo.
|
|
|
|
Nota come utilizzerà **tanti blocchi transformer quanto indicato** e che ogni blocco transformer utilizza una rete di attenzione multi-testa, una rete feed forward e diverse normalizzazioni. Quindi, se vengono utilizzati 12 blocchi transformer, moltiplica questo per 12.
|
|
|
|
Inoltre, uno strato di **normalizzazione** è aggiunto **prima** dell'**output** e uno strato lineare finale è applicato alla fine per ottenere i risultati con le dimensioni appropriate. Nota come ogni vettore finale abbia la dimensione del vocabolario utilizzato. Questo perché sta cercando di ottenere una probabilità per ogni possibile token all'interno del vocabolario.
|
|
{% endhint %}
|
|
|
|
## Numero di Parametri da Addestrare
|
|
|
|
Avendo definito la struttura GPT, è possibile scoprire il numero di parametri da addestrare:
|
|
```python
|
|
GPT_CONFIG_124M = {
|
|
"vocab_size": 50257, # Vocabulary size
|
|
"context_length": 1024, # Context length
|
|
"emb_dim": 768, # Embedding dimension
|
|
"n_heads": 12, # Number of attention heads
|
|
"n_layers": 12, # Number of layers
|
|
"drop_rate": 0.1, # Dropout rate
|
|
"qkv_bias": False # Query-Key-Value bias
|
|
}
|
|
|
|
model = GPTModel(GPT_CONFIG_124M)
|
|
total_params = sum(p.numel() for p in model.parameters())
|
|
print(f"Total number of parameters: {total_params:,}")
|
|
# Total number of parameters: 163,009,536
|
|
```
|
|
### **Calcolo Passo-Passo**
|
|
|
|
#### **1. Strati di Embedding: Token Embedding & Position Embedding**
|
|
|
|
* **Strato:** `nn.Embedding(vocab_size, emb_dim)`
|
|
* **Parametri:** `vocab_size * emb_dim`
|
|
```python
|
|
token_embedding_params = 50257 * 768 = 38,597,376
|
|
```
|
|
* **Layer:** `nn.Embedding(context_length, emb_dim)`
|
|
* **Parameters:** `context_length * emb_dim`
|
|
```python
|
|
position_embedding_params = 1024 * 768 = 786,432
|
|
```
|
|
**Parametri Totali di Embedding**
|
|
```python
|
|
embedding_params = token_embedding_params + position_embedding_params
|
|
embedding_params = 38,597,376 + 786,432 = 39,383,808
|
|
```
|
|
#### **2. Blocchi Transformer**
|
|
|
|
Ci sono 12 blocchi transformer, quindi calcoleremo i parametri per un blocco e poi moltiplicheremo per 12.
|
|
|
|
**Parametri per Blocco Transformer**
|
|
|
|
**a. Attenzione Multi-Testa**
|
|
|
|
* **Componenti:**
|
|
* **Strato Lineare Query (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
|
* **Strato Lineare Chiave (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
|
* **Strato Lineare Valore (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
|
* **Proiezione di Uscita (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
|
|
* **Calcoli:**
|
|
* **Ognuno di `W_query`, `W_key`, `W_value`:**
|
|
|
|
```python
|
|
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
|
|
```
|
|
|
|
Poiché ci sono tre di questi strati:
|
|
|
|
```python
|
|
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
|
|
```
|
|
* **Proiezione di Uscita (`out_proj`):**
|
|
|
|
```python
|
|
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
|
|
```
|
|
* **Totale Parametri di Attenzione Multi-Testa:**
|
|
|
|
```python
|
|
mha_params = total_qkv_params + out_proj_params
|
|
mha_params = 1,769,472 + 590,592 = 2,360,064
|
|
```
|
|
|
|
**b. Rete FeedForward**
|
|
|
|
* **Componenti:**
|
|
* **Primo Strato Lineare:** `nn.Linear(emb_dim, 4 * emb_dim)`
|
|
* **Secondo Strato Lineare:** `nn.Linear(4 * emb_dim, emb_dim)`
|
|
* **Calcoli:**
|
|
* **Primo Strato Lineare:**
|
|
|
|
```python
|
|
ff_first_layer_params = (emb_dim * 4 * emb_dim) + (4 * emb_dim)
|
|
ff_first_layer_params = (768 * 3072) + 3072 = 2,359,296 + 3,072 = 2,362,368
|
|
```
|
|
* **Secondo Strato Lineare:**
|
|
|
|
```python
|
|
ff_second_layer_params = (4 * emb_dim * emb_dim) + emb_dim
|
|
ff_second_layer_params = (3072 * 768) + 768 = 2,359,296 + 768 = 2,360,064
|
|
```
|
|
* **Totale Parametri FeedForward:**
|
|
|
|
```python
|
|
ff_params = ff_first_layer_params + ff_second_layer_params
|
|
ff_params = 2,362,368 + 2,360,064 = 4,722,432
|
|
```
|
|
|
|
**c. Normalizzazioni dei Livelli**
|
|
|
|
* **Componenti:**
|
|
* Due istanze di `LayerNorm` per blocco.
|
|
* Ogni `LayerNorm` ha `2 * emb_dim` parametri (scala e traslazione).
|
|
* **Calcoli:**
|
|
|
|
```python
|
|
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
|
|
```
|
|
|
|
**d. Totale Parametri per Blocco Transformer**
|
|
```python
|
|
pythonCopy codeparams_per_block = mha_params + ff_params + layer_norm_params_per_block
|
|
params_per_block = 2,360,064 + 4,722,432 + 3,072 = 7,085,568
|
|
```
|
|
**Parametri Totali per Tutti i Blocchi Trasformatori**
|
|
```python
|
|
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
|
|
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
|
|
```
|
|
#### **3. Livelli Finali**
|
|
|
|
**a. Normalizzazione del Livello Finale**
|
|
|
|
* **Parametri:** `2 * emb_dim` (scala e traslazione)
|
|
```python
|
|
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
|
|
```
|
|
**b. Strato di Proiezione dell'Uscita (`out_head`)**
|
|
|
|
* **Strato:** `nn.Linear(emb_dim, vocab_size, bias=False)`
|
|
* **Parametri:** `emb_dim * vocab_size`
|
|
```python
|
|
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
|
|
```
|
|
#### **4. Riepilogando Tutti i Parametri**
|
|
```python
|
|
pythonCopy codetotal_params = (
|
|
embedding_params +
|
|
total_transformer_blocks_params +
|
|
final_layer_norm_params +
|
|
output_projection_params
|
|
)
|
|
total_params = (
|
|
39,383,808 +
|
|
85,026,816 +
|
|
1,536 +
|
|
38,597,376
|
|
)
|
|
total_params = 163,009,536
|
|
```
|
|
## Genera Testo
|
|
|
|
Avere un modello che prevede il prossimo token come quello precedente, è sufficiente prendere i valori dell'ultimo token dall'output (poiché saranno quelli del token previsto), che saranno un **valore per voce nel vocabolario** e poi utilizzare la funzione `softmax` per normalizzare le dimensioni in probabilità che sommano 1 e poi ottenere l'indice della voce più grande, che sarà l'indice della parola all'interno del vocabolario.
|
|
|
|
Codice da [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01\_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01\_main-chapter-code/ch04.ipynb):
|
|
```python
|
|
def generate_text_simple(model, idx, max_new_tokens, context_size):
|
|
# idx is (batch, n_tokens) array of indices in the current context
|
|
for _ in range(max_new_tokens):
|
|
|
|
# Crop current context if it exceeds the supported context size
|
|
# E.g., if LLM supports only 5 tokens, and the context size is 10
|
|
# then only the last 5 tokens are used as context
|
|
idx_cond = idx[:, -context_size:]
|
|
|
|
# Get the predictions
|
|
with torch.no_grad():
|
|
logits = model(idx_cond)
|
|
|
|
# Focus only on the last time step
|
|
# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
|
|
logits = logits[:, -1, :]
|
|
|
|
# Apply softmax to get probabilities
|
|
probas = torch.softmax(logits, dim=-1) # (batch, vocab_size)
|
|
|
|
# Get the idx of the vocab entry with the highest probability value
|
|
idx_next = torch.argmax(probas, dim=-1, keepdim=True) # (batch, 1)
|
|
|
|
# Append sampled index to the running sequence
|
|
idx = torch.cat((idx, idx_next), dim=1) # (batch, n_tokens+1)
|
|
|
|
return idx
|
|
|
|
|
|
start_context = "Hello, I am"
|
|
|
|
encoded = tokenizer.encode(start_context)
|
|
print("encoded:", encoded)
|
|
|
|
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
|
|
print("encoded_tensor.shape:", encoded_tensor.shape)
|
|
|
|
model.eval() # disable dropout
|
|
|
|
out = generate_text_simple(
|
|
model=model,
|
|
idx=encoded_tensor,
|
|
max_new_tokens=6,
|
|
context_size=GPT_CONFIG_124M["context_length"]
|
|
)
|
|
|
|
print("Output:", out)
|
|
print("Output length:", len(out[0]))
|
|
```
|
|
## Riferimenti
|
|
|
|
* [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|