Die doel van hierdie vyfde fase is baie eenvoudig: Ontwikkel die argitektuur van die volle LLM. Sit alles saam, pas al die lae toe en skep al die funksies om teks te genereer of teks na ID's en terug te transformeer.
Hierdie argitektuur sal gebruik word vir beide, opleiding en voorspellings van teks nadat dit opgelei is.
Invoer (Getokeniseerde Teks): Die proses begin met getokeniseerde teks, wat in numeriese voorstellings omgeskakel word.
Token Inbed en Posisionele Inbed Laag: Die getokeniseerde teks word deur 'n token inbed laag en 'n posisionele inbed laag gestuur, wat die posisie van tokens in 'n volgorde vasvang, krities vir die begrip van woordorde.
Transformer Blokke: Die model bevat 12 transformer blokke, elk met verskeie lae. Hierdie blokke herhaal die volgende volgorde:
Gemaskerde Multi-Kop Aandag: Laat die model toe om op verskillende dele van die invoerteks gelyktydig te fokus.
Laag Normalisering: 'n Normalisering stap om opleiding te stabiliseer en te verbeter.
Voed Voor Laag: Verantwoordelik vir die verwerking van die inligting van die aandaglaag en om voorspellings oor die volgende token te maak.
Dropout Lae: Hierdie lae voorkom oorpassing deur eenhede tydens opleiding lukraak te laat val.
Finale Uitvoer Laag: Die model lewer 'n 4x50,257-dimensionele tensor, waar 50,257 die grootte van die woordeskat verteenwoordig. Elke ry in hierdie tensor kom ooreen met 'n vektor wat die model gebruik om die volgende woord in die volgorde te voorspel.
Doel: Die doel is om hierdie inbedings te neem en dit terug in teks om te skakel. Spesifiek, die laaste ry van die uitvoer word gebruik om die volgende woord te genereer, wat as "vorentoe" in hierdie diagram verteenwoordig word.
Kode voorstelling
import torchimport torch.nn as nnimport tiktokenclassGELU(nn.Module):def__init__(self):super().__init__()defforward(self,x):return0.5* x * (1+ torch.tanh(torch.sqrt(torch.tensor(2.0/ torch.pi)) *(x +0.044715* torch.pow(x, 3))))classFeedForward(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"]),)defforward(self,x):return self.layers(x)classMultiHeadAttention(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_outself.num_heads = num_headsself.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dimself.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 outputsself.dropout = nn.Dropout(dropout)self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))defforward(self,x):b, num_tokens, d_in = x.shapekeys = 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 maskattn_scores = queries @ keys.transpose(2, 3)# Dot product for each head# Original mask truncated to the number of tokens and converted to booleanmask_bool = self.mask.bool()[:num_tokens,:num_tokens]# Use the mask to fill attention scoresattn_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_dimcontext_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)context_vec = self.out_proj(context_vec)# optional projectionreturn context_vecclassLayerNorm(nn.Module):def__init__(self,emb_dim):super().__init__()self.eps =1e-5self.scale = nn.Parameter(torch.ones(emb_dim))self.shift = nn.Parameter(torch.zeros(emb_dim))defforward(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.shiftclassTransformerBlock(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"])defforward(self,x):# Shortcut connection for attention blockshortcut = xx = 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 blockshortcut = xx = self.norm2(x)x = self.ff(x)x = self.drop_shortcut(x)x = x + shortcut # Add the original input backreturn xclassGPTModel(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 _ inrange(cfg["n_layers"])])self.final_norm =LayerNorm(cfg["emb_dim"])self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)defforward(self,in_idx):batch_size, seq_len = in_idx.shapetok_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 logitsGPT_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)
GELU Aktivering Funksie
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classGELU(nn.Module):def__init__(self):super().__init__()defforward(self,x):return0.5* x * (1+ torch.tanh(torch.sqrt(torch.tensor(2.0/ torch.pi)) *(x +0.044715* torch.pow(x, 3))))
Doel en Funksionaliteit
GELU (Gaussian Error Linear Unit): 'n Aktiveringsfunksie wat nie-lineariteit in die model inbring.
Gladde Aktivering: Anders as ReLU, wat negatiewe insette op nul stel, kaart GELU insette glad aan uitsette toe, wat klein, nie-nul waardes vir negatiewe insette toelaat.
Wiskundige Definisie:
Die doel van die gebruik van hierdie funksie na lineêre lae binne die FeedForward-laag is om die lineêre data te verander na nie-lineêr om die model in staat te stel om komplekse, nie-lineêre verhoudings te leer.
FeedForward Neurale Netwerk
Vorms is as kommentaar bygevoeg om die vorms van matrikse beter te verstaan:
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classFeedForward(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"]),)defforward(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)
Doel en Funksionaliteit
Posisiegewys FeedForward Netwerk: Pas 'n twee-laag ten volle verbind netwerk op elke posisie apart en identies toe.
Laag Besonderhede:
Eerste Lineêre Laag: Brei die dimensie uit van emb_dim na 4 * emb_dim.
GELU Aktivering: Pas nie-lineariteit toe.
Tweede Lineêre Laag: Verminder die dimensie terug na emb_dim.
Soos jy kan sien, gebruik die Feed Forward netwerk 3 lae. Die eerste een is 'n lineêre laag wat die dimensies met 4 sal vermenigvuldig met behulp van lineêre gewigte (parameters om binne die model te train). Dan word die GELU-funksie in al daardie dimensies gebruik om nie-lineêre variasies toe te pas om ryker verteenwoordigings te vang en uiteindelik word 'n ander lineêre laag gebruik om terug te keer na die oorspronklike grootte van dimensies.
Multi-Head Aandag Meganisme
Dit is reeds in 'n vroeëre afdeling verduidelik.
Doel en Funksionaliteit
Multi-Head Self-Attention: Laat die model toe om op verskillende posisies binne die invoer volgorde te fokus wanneer 'n token gekodeer word.
Belangrike Komponente:
Vrae, Sleutels, Waardes: Lineêre projeksies van die invoer, gebruik om aandag punte te bereken.
Koppe: Meervoudige aandag meganismes wat parallel loop (num_heads), elk met 'n verminderde dimensie (head_dim).
Aandag Punte: Bereken as die skaalproduk van vrae en sleutels, geskaal en gemaskeer.
Maskering: 'n Oorsaaklike masker word toegepas om te voorkom dat die model na toekomstige tokens aandag gee (belangrik vir outoregressiewe modelle soos GPT).
Aandag Gewigte: Softmax van die gemaskeerde en geskaalde aandag punte.
Konteks Vektor: Gewigte som van die waardes, volgens aandag gewigte.
Uitset Projektering: Lineêre laag om die uitsette van al die koppe te kombineer.
Die doel van hierdie netwerk is om die verhoudings tussen tokens in dieselfde konteks te vind. Boonop word die tokens in verskillende koppe verdeel om oorfitting te voorkom, alhoewel die finale verhoudings wat per kop gevind word aan die einde van hierdie netwerk gekombineer word.
Boonop, tydens opleiding, word 'n oorsaaklike masker toegepas sodat latere tokens nie in ag geneem word wanneer die spesifieke verhoudings met 'n token gekyk word nie en 'n dropout word ook toegepas om oorfitting te voorkom.
Laag Normalisering
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classLayerNorm(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))defforward(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
Doel en Funksionaliteit
Laag Normalisering: 'n Tegniek wat gebruik word om die insette oor die kenmerke (embedding dimensies) vir elke individuele voorbeeld in 'n bondel te normaliseer.
Komponente:
eps: 'n Klein konstante (1e-5) wat by die variansie gevoeg word om deling deur nul tydens normalisering te voorkom.
scale en shift: Leerbare parameters (nn.Parameter) wat die model toelaat om die genormaliseerde uitset te skaal en te verskuif. Hulle word onderskeidelik geinitialiseer na een en nul.
Normalisering Proses:
Bereken Gemiddelde (mean): Bereken die gemiddelde van die inset x oor die embedding dimensie (dim=-1), terwyl die dimensie vir broadcasting behou word (keepdim=True).
Bereken Variansie (var): Bereken die variansie van x oor die embedding dimensie, terwyl die dimensie ook behou word. Die unbiased=False parameter verseker dat die variansie bereken word met die bevooroordeelde skatter (deling deur N in plaas van N-1), wat toepaslik is wanneer daar oor kenmerke eerder as monsters genormaliseer word.
Normaliseer (norm_x): Trek die gemiddelde van x af en deel deur die vierkantswortel van die variansie plus eps.
Skaal en Verskuif: Pas die leerbare scale en shift parameters toe op die genormaliseerde uitset.
Die doel is om 'n gemiddelde van 0 met 'n variansie van 1 oor alle dimensies van dieselfde token te verseker. Die doel hiervan is om die opleiding van diep neurale netwerke te stabiliseer deur die interne kovariate verskuiwing te verminder, wat verwys na die verandering in die verspreiding van netwerk aktiverings as gevolg van die opdatering van parameters tydens opleiding.
Transformer Blok
Vorms is as kommentaar bygevoeg om die vorms van matrikse beter te verstaan:
Samestelling van Lae: Kombineer multi-head attention, feedforward netwerk, laanormalisering, en residuele verbindings.
Laanormalisering: Toegepas voor die aandag en feedforward lae vir stabiele opleiding.
Residuele Verbindings (Kortpaaie): Voeg die invoer van 'n laag by sy uitvoer om die gradiëntvloei te verbeter en die opleiding van diep netwerke moontlik te maak.
Dropout: Toegepas na aandag en feedforward lae vir regulering.
Stap-vir-Stap Funksionaliteit
Eerste Residuele Pad (Self-Aandagtigheid):
Invoer (shortcut): Stoor die oorspronklike invoer vir die residuele verbinding.
Laag Norm (norm1): Normaliseer die invoer.
Multi-Head Attention (att): Pas self-aandagtigheid toe.
Dropout (drop_shortcut): Pas dropout toe vir regulering.
Voeg Residueel By (x + shortcut): Kombineer met die oorspronklike invoer.
Tweedee Residuele Pad (FeedForward):
Invoer (shortcut): Stoor die opgedateerde invoer vir die volgende residuele verbinding.
Laag Norm (norm2): Normaliseer die invoer.
FeedForward Netwerk (ff): Pas die feedforward transformasie toe.
Dropout (drop_shortcut): Pas dropout toe.
Voeg Residueel By (x + shortcut): Kombineer met die invoer van die eerste residuele pad.
Die transformer blok groepeer al die netwerke saam en pas 'n paar normalisering en dropouts toe om die opleidingsstabiliteit en resultate te verbeter.
Let op hoe dropouts gedoen word na die gebruik van elke netwerk terwyl normalisering voor toegepas word.
Boonop gebruik dit ook kortpaaie wat bestaan uit die uitvoer van 'n netwerk by sy invoer te voeg. Dit help om die verdwynende gradiëntprobleem te voorkom deur te verseker dat aanvanklike lae "net soveel" bydra as die laaste.
GPTModel
Vorms is as kommentaar bygevoeg om die vorms van matrikse beter te verstaan:
Token Inbedings (tok_emb): Converteer token-indekse in inbedings. Ter herinnering, dit is die gewigte wat aan elke dimensie van elke token in die woordeskat gegee word.
Posisionele Inbedings (pos_emb): Voeg posisionele inligting by die inbedings om die volgorde van tokens vas te vang. Ter herinnering, dit is die gewigte wat aan tokens gegee word volgens hul posisie in die teks.
Dropout (drop_emb): Toegepas op inbedings vir regularisering.
Transformer Blokke (trf_blocks): Stapel van n_layers transformer blokke om inbedings te verwerk.
Finale Normalisering (final_norm): Laag normalisering voor die uitvoerlaag.
Uitvoerlaag (out_head): Projek die finale verborge toestande na die woordeskatgrootte om logits vir voorspelling te produseer.
Die doel van hierdie klas is om al die ander genoemde netwerke te gebruik om die volgende token in 'n volgorde te voorspel, wat fundamenteel is vir take soos teksgenerasie.
Let op hoe dit soveel transformer blokke as aangedui sal gebruik en dat elke transformer blok een multi-head attestasienet, een feed forward-net en verskeie normaliserings gebruik. So as 12 transformer blokke gebruik word, vermenigvuldig dit met 12.
Boonop word 'n normalisering laag voor die uitvoer bygevoeg en 'n finale lineêre laag word aan die einde toegepas om die resultate met die regte dimensies te verkry. Let op hoe elke finale vektor die grootte van die gebruikte woordeskat het. Dit is omdat dit probeer om 'n waarskynlikheid per moontlike token binne die woordeskat te kry.
Aantal Parameters om te oefen
Met die GPT-struktuur gedefinieer, is dit moontlik om die aantal parameters om te oefen te vind:
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
Met 'n model wat die volgende token voorspel soos die vorige, is dit net nodig om die laaste tokenwaardes van die uitvoer te neem (aangesien dit die waardes van die voorspelde token sal wees), wat 'n waarde per inskrywing in die woordeskat sal wees en dan die softmax funksie te gebruik om die dimensies te normaliseer in waarskynlikhede wat 1 optel en dan die indeks van die grootste inskrywing te kry, wat die indeks van die woord binne die woordeskat sal wees.
defgenerate_text_simple(model,idx,max_new_tokens,context_size):# idx is (batch, n_tokens) array of indices in the current contextfor _ inrange(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 contextidx_cond = idx[:,-context_size:]# Get the predictionswith 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 probabilitiesprobas = torch.softmax(logits, dim=-1)# (batch, vocab_size)# Get the idx of the vocab entry with the highest probability valueidx_next = torch.argmax(probas, dim=-1, keepdim=True)# (batch, 1)# Append sampled index to the running sequenceidx = torch.cat((idx, idx_next), dim=1)# (batch, n_tokens+1)return idxstart_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 dropoutout =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]))