# 5. Architettura LLM ## Architettura LLM > [!TIP] > 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. 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:

https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31

1. **Input (Testo Tokenizzato)**: Il processo inizia con testo tokenizzato, che viene convertito in rappresentazioni numeriche. 2. **Layer di Embedding dei Token e 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 "forward" 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 gli input agli output in modo fluido, consentendo valori piccoli e diversi da zero per gli input negativi. - **Definizione Matematica:**
> [!NOTE] > L'obiettivo dell'uso di questa funzione dopo i livelli lineari all'interno del livello FeedForward è cambiare i dati lineari in dati non lineari per consentire al modello di apprendere relazioni complesse e non lineari. ### **Rete Neurale FeedForward** _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 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 a livello di posizione:** Applica una rete completamente connessa a due strati a ciascuna posizione separatamente e in modo identico. - **Dettagli del livello:** - **Primo Livello Lineare:** Espande la dimensionalità da `emb_dim` a `4 * emb_dim`. - **Attivazione GELU:** Applica non linearità. - **Secondo Livello Lineare:** Riduce la dimensionalità di nuovo a `emb_dim`. > [!NOTE] > 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. ### **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:** Viene applicata una maschera causale 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. > [!NOTE] > 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** in modo che 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**. ### **Normalizzazione** del Livello ```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 a uno e zero, rispettivamente. - **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 anch'essa la dimensione. Il parametro `unbiased=False` garantisce che la varianza venga calcolata utilizzando l'estimatore biased (dividendo per `N` invece di `N-1`), 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. > [!NOTE] > 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 il cambiamento interno della covariata, che si riferisce al cambiamento nella distribuzione delle attivazioni della rete a causa dell'aggiornamento dei parametri durante l'addestramento. ### **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. - **Multi-Head Attention (`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. - **FeedForward Network (`ff`):** Applica la trasformazione feedforward. - **Dropout (`drop_shortcut`):** Applica dropout. - **Aggiungi Residuo (`x + shortcut`):** Combina con l'input del primo percorso residuo. > [!NOTE] > 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 vengano 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. ### **GPTModel** _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 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. - **Transformer Blocks (`trf_blocks`):** Stack di `n_layers` blocchi transformer per elaborare gli embedding. - **Normalizzazione Finale (`final_norm`):** Normalizzazione dello strato prima dello strato di output. - **Output Layer (`out_head`):** Proietta gli stati nascosti finali alla dimensione del vocabolario per produrre logits per la previsione. > [!NOTE] > 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 attivazione 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. ## Numero di Parametri da addestrare Avendo definita 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 di Embedding Totali** ```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 Blocchi 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 ``` - **Parametri Totali 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 ``` - **Parametri Totali 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. Parametri Totali 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 Avendo un modello che prevede il token successivo 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)