mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
947 lines
40 KiB
Markdown
947 lines
40 KiB
Markdown
# 6. Pré-entraînement et chargement des modèles
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Génération de texte
|
||
|
||
Pour entraîner un modèle, nous aurons besoin que ce modèle soit capable de générer de nouveaux tokens. Ensuite, nous comparerons les tokens générés avec ceux attendus afin d'entraîner le modèle à **apprendre les tokens qu'il doit générer**.
|
||
|
||
Comme dans les exemples précédents où nous avons déjà prédit certains tokens, il est possible de réutiliser cette fonction à cette fin.
|
||
|
||
> [!TIP]
|
||
> L'objectif de cette sixième phase est très simple : **Entraîner le modèle depuis le début**. Pour cela, l'architecture LLM précédente sera utilisée avec quelques boucles parcourant les ensembles de données en utilisant les fonctions de perte et l'optimiseur définis pour entraîner tous les paramètres du modèle.
|
||
|
||
## Évaluation de texte
|
||
|
||
Pour effectuer un entraînement correct, il est nécessaire de mesurer les prédictions obtenues pour le token attendu. L'objectif de l'entraînement est de maximiser la probabilité du token correct, ce qui implique d'augmenter sa probabilité par rapport aux autres tokens.
|
||
|
||
Pour maximiser la probabilité du token correct, les poids du modèle doivent être modifiés afin que cette probabilité soit maximisée. Les mises à jour des poids se font via **backpropagation**. Cela nécessite une **fonction de perte à maximiser**. Dans ce cas, la fonction sera la **différence entre la prédiction effectuée et celle désirée**.
|
||
|
||
Cependant, au lieu de travailler avec les prédictions brutes, il travaillera avec un logarithme de base n. Ainsi, si la prédiction actuelle du token attendu était 7.4541e-05, le logarithme naturel (base *e*) de **7.4541e-05** est d'environ **-9.5042**.\
|
||
Ensuite, pour chaque entrée avec une longueur de contexte de 5 tokens par exemple, le modèle devra prédire 5 tokens, les 4 premiers tokens étant le dernier de l'entrée et le cinquième étant celui prédit. Par conséquent, pour chaque entrée, nous aurons 5 prédictions dans ce cas (même si les 4 premiers étaient dans l'entrée, le modèle ne le sait pas) avec 5 tokens attendus et donc 5 probabilités à maximiser.
|
||
|
||
Par conséquent, après avoir effectué le logarithme naturel sur chaque prédiction, la **moyenne** est calculée, le **symbole moins retiré** (c'est ce qu'on appelle _cross entropy loss_) et c'est le **nombre à réduire aussi près de 0 que possible** car le logarithme naturel de 1 est 0 :
|
||
|
||
<figure><img src="../../images/image (10) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233">https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233</a></p></figcaption></figure>
|
||
|
||
Une autre façon de mesurer la qualité du modèle est appelée perplexité. **Perplexité** est une métrique utilisée pour évaluer à quel point un modèle de probabilité prédit un échantillon. Dans la modélisation du langage, elle représente **l'incertitude du modèle** lors de la prédiction du prochain token dans une séquence.\
|
||
Par exemple, une valeur de perplexité de 48725 signifie que lorsqu'il doit prédire un token, il n'est pas sûr de savoir lequel parmi 48 725 tokens dans le vocabulaire est le bon.
|
||
|
||
## Exemple de pré-entraînement
|
||
|
||
Voici le code initial proposé dans [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb) parfois légèrement modifié
|
||
|
||
<details>
|
||
|
||
<summary>Code précédent utilisé ici mais déjà expliqué dans les sections précédentes</summary>
|
||
```python
|
||
"""
|
||
This is code explained before so it won't be exaplained
|
||
"""
|
||
|
||
import tiktoken
|
||
import torch
|
||
import torch.nn as nn
|
||
from torch.utils.data import Dataset, DataLoader
|
||
|
||
|
||
class GPTDatasetV1(Dataset):
|
||
def __init__(self, txt, tokenizer, max_length, stride):
|
||
self.input_ids = []
|
||
self.target_ids = []
|
||
|
||
# Tokenize the entire text
|
||
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
|
||
|
||
# Use a sliding window to chunk the book into overlapping sequences of max_length
|
||
for i in range(0, len(token_ids) - max_length, stride):
|
||
input_chunk = token_ids[i:i + max_length]
|
||
target_chunk = token_ids[i + 1: i + max_length + 1]
|
||
self.input_ids.append(torch.tensor(input_chunk))
|
||
self.target_ids.append(torch.tensor(target_chunk))
|
||
|
||
def __len__(self):
|
||
return len(self.input_ids)
|
||
|
||
def __getitem__(self, idx):
|
||
return self.input_ids[idx], self.target_ids[idx]
|
||
|
||
|
||
def create_dataloader_v1(txt, batch_size=4, max_length=256,
|
||
stride=128, shuffle=True, drop_last=True, num_workers=0):
|
||
# Initialize the tokenizer
|
||
tokenizer = tiktoken.get_encoding("gpt2")
|
||
|
||
# Create dataset
|
||
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
|
||
|
||
# Create dataloader
|
||
dataloader = DataLoader(
|
||
dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last, num_workers=num_workers)
|
||
|
||
return dataloader
|
||
|
||
|
||
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 n_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.reshape(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 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 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
|
||
```
|
||
</details>
|
||
```python
|
||
# Download contents to train the data with
|
||
import os
|
||
import urllib.request
|
||
|
||
file_path = "the-verdict.txt"
|
||
url = "https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt"
|
||
|
||
if not os.path.exists(file_path):
|
||
with urllib.request.urlopen(url) as response:
|
||
text_data = response.read().decode('utf-8')
|
||
with open(file_path, "w", encoding="utf-8") as file:
|
||
file.write(text_data)
|
||
else:
|
||
with open(file_path, "r", encoding="utf-8") as file:
|
||
text_data = file.read()
|
||
|
||
total_characters = len(text_data)
|
||
tokenizer = tiktoken.get_encoding("gpt2")
|
||
total_tokens = len(tokenizer.encode(text_data))
|
||
|
||
print("Data downloaded")
|
||
print("Characters:", total_characters)
|
||
print("Tokens:", total_tokens)
|
||
|
||
# Model initialization
|
||
GPT_CONFIG_124M = {
|
||
"vocab_size": 50257, # Vocabulary size
|
||
"context_length": 256, # Shortened context length (orig: 1024)
|
||
"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)
|
||
model.eval()
|
||
print ("Model initialized")
|
||
|
||
|
||
# Functions to transform from tokens to ids and from to ids to tokens
|
||
def text_to_token_ids(text, tokenizer):
|
||
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
|
||
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
|
||
return encoded_tensor
|
||
|
||
def token_ids_to_text(token_ids, tokenizer):
|
||
flat = token_ids.squeeze(0) # remove batch dimension
|
||
return tokenizer.decode(flat.tolist())
|
||
|
||
|
||
|
||
# Define loss functions
|
||
def calc_loss_batch(input_batch, target_batch, model, device):
|
||
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
|
||
logits = model(input_batch)
|
||
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
|
||
return loss
|
||
|
||
|
||
def calc_loss_loader(data_loader, model, device, num_batches=None):
|
||
total_loss = 0.
|
||
if len(data_loader) == 0:
|
||
return float("nan")
|
||
elif num_batches is None:
|
||
num_batches = len(data_loader)
|
||
else:
|
||
# Reduce the number of batches to match the total number of batches in the data loader
|
||
# if num_batches exceeds the number of batches in the data loader
|
||
num_batches = min(num_batches, len(data_loader))
|
||
for i, (input_batch, target_batch) in enumerate(data_loader):
|
||
if i < num_batches:
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
total_loss += loss.item()
|
||
else:
|
||
break
|
||
return total_loss / num_batches
|
||
|
||
|
||
# Apply Train/validation ratio and create dataloaders
|
||
train_ratio = 0.90
|
||
split_idx = int(train_ratio * len(text_data))
|
||
train_data = text_data[:split_idx]
|
||
val_data = text_data[split_idx:]
|
||
|
||
torch.manual_seed(123)
|
||
|
||
train_loader = create_dataloader_v1(
|
||
train_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=True,
|
||
shuffle=True,
|
||
num_workers=0
|
||
)
|
||
|
||
val_loader = create_dataloader_v1(
|
||
val_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=False,
|
||
shuffle=False,
|
||
num_workers=0
|
||
)
|
||
|
||
|
||
# Sanity checks
|
||
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the training loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"increase the `training_ratio`")
|
||
|
||
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the validation loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"decrease the `training_ratio`")
|
||
|
||
print("Train loader:")
|
||
for x, y in train_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
print("\nValidation loader:")
|
||
for x, y in val_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
train_tokens = 0
|
||
for input_batch, target_batch in train_loader:
|
||
train_tokens += input_batch.numel()
|
||
|
||
val_tokens = 0
|
||
for input_batch, target_batch in val_loader:
|
||
val_tokens += input_batch.numel()
|
||
|
||
print("Training tokens:", train_tokens)
|
||
print("Validation tokens:", val_tokens)
|
||
print("All tokens:", train_tokens + val_tokens)
|
||
|
||
|
||
# Indicate the device to use
|
||
if torch.cuda.is_available():
|
||
device = torch.device("cuda")
|
||
elif torch.backends.mps.is_available():
|
||
device = torch.device("mps")
|
||
else:
|
||
device = torch.device("cpu")
|
||
|
||
print(f"Using {device} device.")
|
||
|
||
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
|
||
|
||
|
||
|
||
# Pre-calculate losses without starting yet
|
||
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
|
||
|
||
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
|
||
train_loss = calc_loss_loader(train_loader, model, device)
|
||
val_loss = calc_loss_loader(val_loader, model, device)
|
||
|
||
print("Training loss:", train_loss)
|
||
print("Validation loss:", val_loss)
|
||
|
||
|
||
# Functions to train the data
|
||
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
|
||
eval_freq, eval_iter, start_context, tokenizer):
|
||
# Initialize lists to track losses and tokens seen
|
||
train_losses, val_losses, track_tokens_seen = [], [], []
|
||
tokens_seen, global_step = 0, -1
|
||
|
||
# Main training loop
|
||
for epoch in range(num_epochs):
|
||
model.train() # Set model to training mode
|
||
|
||
for input_batch, target_batch in train_loader:
|
||
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
loss.backward() # Calculate loss gradients
|
||
optimizer.step() # Update model weights using loss gradients
|
||
tokens_seen += input_batch.numel()
|
||
global_step += 1
|
||
|
||
# Optional evaluation step
|
||
if global_step % eval_freq == 0:
|
||
train_loss, val_loss = evaluate_model(
|
||
model, train_loader, val_loader, device, eval_iter)
|
||
train_losses.append(train_loss)
|
||
val_losses.append(val_loss)
|
||
track_tokens_seen.append(tokens_seen)
|
||
print(f"Ep {epoch+1} (Step {global_step:06d}): "
|
||
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
|
||
|
||
# Print a sample text after each epoch
|
||
generate_and_print_sample(
|
||
model, tokenizer, device, start_context
|
||
)
|
||
|
||
return train_losses, val_losses, track_tokens_seen
|
||
|
||
|
||
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
|
||
model.eval()
|
||
with torch.no_grad():
|
||
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
|
||
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
|
||
model.train()
|
||
return train_loss, val_loss
|
||
|
||
|
||
def generate_and_print_sample(model, tokenizer, device, start_context):
|
||
model.eval()
|
||
context_size = model.pos_emb.weight.shape[0]
|
||
encoded = text_to_token_ids(start_context, tokenizer).to(device)
|
||
with torch.no_grad():
|
||
token_ids = generate_text(
|
||
model=model, idx=encoded,
|
||
max_new_tokens=50, context_size=context_size
|
||
)
|
||
decoded_text = token_ids_to_text(token_ids, tokenizer)
|
||
print(decoded_text.replace("\n", " ")) # Compact print format
|
||
model.train()
|
||
|
||
|
||
# Start training!
|
||
import time
|
||
start_time = time.time()
|
||
|
||
torch.manual_seed(123)
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.to(device)
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
|
||
|
||
num_epochs = 10
|
||
train_losses, val_losses, tokens_seen = train_model_simple(
|
||
model, train_loader, val_loader, optimizer, device,
|
||
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
|
||
start_context="Every effort moves you", tokenizer=tokenizer
|
||
)
|
||
|
||
end_time = time.time()
|
||
execution_time_minutes = (end_time - start_time) / 60
|
||
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
|
||
|
||
|
||
|
||
# Show graphics with the training process
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.ticker import MaxNLocator
|
||
import math
|
||
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
|
||
fig, ax1 = plt.subplots(figsize=(5, 3))
|
||
ax1.plot(epochs_seen, train_losses, label="Training loss")
|
||
ax1.plot(
|
||
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
|
||
)
|
||
ax1.set_xlabel("Epochs")
|
||
ax1.set_ylabel("Loss")
|
||
ax1.legend(loc="upper right")
|
||
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
|
||
ax2 = ax1.twiny()
|
||
ax2.plot(tokens_seen, train_losses, alpha=0)
|
||
ax2.set_xlabel("Tokens seen")
|
||
fig.tight_layout()
|
||
plt.show()
|
||
|
||
# Compute perplexity from the loss values
|
||
train_ppls = [math.exp(loss) for loss in train_losses]
|
||
val_ppls = [math.exp(loss) for loss in val_losses]
|
||
# Plot perplexity over tokens seen
|
||
plt.figure()
|
||
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
|
||
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
|
||
plt.xlabel('Tokens Seen')
|
||
plt.ylabel('Perplexity')
|
||
plt.title('Perplexity over Training')
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
|
||
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
|
||
|
||
|
||
torch.save({
|
||
"model_state_dict": model.state_dict(),
|
||
"optimizer_state_dict": optimizer.state_dict(),
|
||
},
|
||
"/tmp/model_and_optimizer.pth"
|
||
)
|
||
```
|
||
### Fonctions pour transformer le texte <--> ids
|
||
|
||
Ce sont quelques fonctions simples qui peuvent être utilisées pour transformer des textes du vocabulaire en ids et vice versa. Cela est nécessaire au début du traitement du texte et à la fin des prédictions :
|
||
```python
|
||
# Functions to transform from tokens to ids and from to ids to tokens
|
||
def text_to_token_ids(text, tokenizer):
|
||
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
|
||
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
|
||
return encoded_tensor
|
||
|
||
def token_ids_to_text(token_ids, tokenizer):
|
||
flat = token_ids.squeeze(0) # remove batch dimension
|
||
return tokenizer.decode(flat.tolist())
|
||
```
|
||
### Générer des fonctions de texte
|
||
|
||
Dans une section précédente, une fonction qui obtenait simplement le **token le plus probable** après avoir obtenu les logits. Cependant, cela signifie que pour chaque entrée, la même sortie sera toujours générée, ce qui la rend très déterministe.
|
||
|
||
La fonction `generate_text` suivante appliquera les concepts de `top-k`, `temperature` et `multinomial`.
|
||
|
||
- Le **`top-k`** signifie que nous allons commencer par réduire à `-inf` toutes les probabilités de tous les tokens sauf pour les k tokens les plus élevés. Donc, si k=3, avant de prendre une décision, seuls les 3 tokens les plus probables auront une probabilité différente de `-inf`.
|
||
- La **`temperature`** signifie que chaque probabilité sera divisée par la valeur de température. Une valeur de `0.1` améliorera la probabilité la plus élevée par rapport à la plus basse, tandis qu'une température de `5`, par exemple, la rendra plus plate. Cela aide à améliorer la variation des réponses que nous aimerions que le LLM ait.
|
||
- Après avoir appliqué la température, une fonction **`softmax`** est appliquée à nouveau pour faire en sorte que tous les tokens restants aient une probabilité totale de 1.
|
||
- Enfin, au lieu de choisir le token avec la plus grande probabilité, la fonction **`multinomial`** est appliquée pour **prédire le prochain token selon les probabilités finales**. Donc, si le token 1 avait 70% de probabilités, le token 2 20% et le token 3 10%, 70% du temps le token 1 sera sélectionné, 20% du temps ce sera le token 2 et 10% du temps ce sera le token 3.
|
||
```python
|
||
# Generate text function
|
||
def generate_text(model, idx, max_new_tokens, context_size, temperature=0.0, top_k=None, eos_id=None):
|
||
|
||
# For-loop is the same as before: Get logits, and only focus on last time step
|
||
for _ in range(max_new_tokens):
|
||
idx_cond = idx[:, -context_size:]
|
||
with torch.no_grad():
|
||
logits = model(idx_cond)
|
||
logits = logits[:, -1, :]
|
||
|
||
# New: Filter logits with top_k sampling
|
||
if top_k is not None:
|
||
# Keep only top_k values
|
||
top_logits, _ = torch.topk(logits, top_k)
|
||
min_val = top_logits[:, -1]
|
||
logits = torch.where(logits < min_val, torch.tensor(float("-inf")).to(logits.device), logits)
|
||
|
||
# New: Apply temperature scaling
|
||
if temperature > 0.0:
|
||
logits = logits / temperature
|
||
|
||
# Apply softmax to get probabilities
|
||
probs = torch.softmax(logits, dim=-1) # (batch_size, context_len)
|
||
|
||
# Sample from the distribution
|
||
idx_next = torch.multinomial(probs, num_samples=1) # (batch_size, 1)
|
||
|
||
# Otherwise same as before: get idx of the vocab entry with the highest logits value
|
||
else:
|
||
idx_next = torch.argmax(logits, dim=-1, keepdim=True) # (batch_size, 1)
|
||
|
||
if idx_next == eos_id: # Stop generating early if end-of-sequence token is encountered and eos_id is specified
|
||
break
|
||
|
||
# Same as before: append sampled index to the running sequence
|
||
idx = torch.cat((idx, idx_next), dim=1) # (batch_size, num_tokens+1)
|
||
|
||
return idx
|
||
```
|
||
> [!TIP]
|
||
> Il existe une alternative courante à `top-k` appelée [**`top-p`**](https://en.wikipedia.org/wiki/Top-p_sampling), également connue sous le nom d'échantillonnage par noyau, qui au lieu de prendre k échantillons avec la plus grande probabilité, **organise** tout le **vocabulaire** résultant par probabilités et **somme** celles-ci de la plus haute probabilité à la plus basse jusqu'à ce qu'un **seuil soit atteint**.
|
||
>
|
||
> Ensuite, **seuls ces mots** du vocabulaire seront considérés en fonction de leurs probabilités relatives.
|
||
>
|
||
> Cela permet de ne pas avoir besoin de sélectionner un nombre d'échantillons `k`, car le k optimal peut être différent dans chaque cas, mais **seulement un seuil**.
|
||
>
|
||
> _Notez que cette amélioration n'est pas incluse dans le code précédent._
|
||
|
||
> [!TIP]
|
||
> Une autre façon d'améliorer le texte généré est d'utiliser **Beam search** au lieu de la recherche gloutonne utilisée dans cet exemple.\
|
||
> Contrairement à la recherche gloutonne, qui sélectionne le mot suivant le plus probable à chaque étape et construit une seule séquence, **la recherche par faisceau garde une trace des 𝑘 k séquences partielles les mieux notées** (appelées "faisceaux") à chaque étape. En explorant plusieurs possibilités simultanément, elle équilibre efficacité et qualité, augmentant les chances de **trouver une meilleure séquence globale** qui pourrait être manquée par l'approche gloutonne en raison de choix précoces et sous-optimaux.
|
||
>
|
||
> _Notez que cette amélioration n'est pas incluse dans le code précédent._
|
||
|
||
### Fonctions de perte
|
||
|
||
La fonction **`calc_loss_batch`** calcule l'entropie croisée d'une prédiction d'un seul lot.\
|
||
La **`calc_loss_loader`** obtient l'entropie croisée de tous les lots et calcule l'**entropie croisée moyenne**.
|
||
```python
|
||
# Define loss functions
|
||
def calc_loss_batch(input_batch, target_batch, model, device):
|
||
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
|
||
logits = model(input_batch)
|
||
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
|
||
return loss
|
||
|
||
def calc_loss_loader(data_loader, model, device, num_batches=None):
|
||
total_loss = 0.
|
||
if len(data_loader) == 0:
|
||
return float("nan")
|
||
elif num_batches is None:
|
||
num_batches = len(data_loader)
|
||
else:
|
||
# Reduce the number of batches to match the total number of batches in the data loader
|
||
# if num_batches exceeds the number of batches in the data loader
|
||
num_batches = min(num_batches, len(data_loader))
|
||
for i, (input_batch, target_batch) in enumerate(data_loader):
|
||
if i < num_batches:
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
total_loss += loss.item()
|
||
else:
|
||
break
|
||
return total_loss / num_batches
|
||
```
|
||
> [!TIP]
|
||
> **Le clipping de gradient** est une technique utilisée pour améliorer **la stabilité de l'entraînement** dans de grands réseaux de neurones en fixant un **seuil maximum** pour les magnitudes des gradients. Lorsque les gradients dépassent ce `max_norm` prédéfini, ils sont réduits proportionnellement pour garantir que les mises à jour des paramètres du modèle restent dans une plage gérable, évitant des problèmes comme les gradients explosifs et assurant un entraînement plus contrôlé et stable.
|
||
>
|
||
> _Notez que cette amélioration n'est pas incluse dans le code précédent._
|
||
>
|
||
> Vérifiez l'exemple suivant :
|
||
|
||
<figure><img src="../../images/image (6) (1).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
### Chargement des données
|
||
|
||
Les fonctions `create_dataloader_v1` et `create_dataloader_v1` ont déjà été discutées dans une section précédente.
|
||
|
||
À partir de là, notez comment il est défini que 90 % du texte sera utilisé pour l'entraînement tandis que 10 % sera utilisé pour la validation et que les deux ensembles sont stockés dans 2 chargeurs de données différents.\
|
||
Notez que parfois, une partie de l'ensemble de données est également laissée pour un ensemble de test afin d'évaluer mieux la performance du modèle.
|
||
|
||
Les deux chargeurs de données utilisent la même taille de lot, longueur maximale, stride et nombre de travailleurs (0 dans ce cas).\
|
||
Les principales différences résident dans les données utilisées par chacun, et le validateur ne supprime pas le dernier ni ne mélange les données car ce n'est pas nécessaire à des fins de validation.
|
||
|
||
De plus, le fait que **le stride soit aussi grand que la longueur du contexte** signifie qu'il n'y aura pas de chevauchement entre les contextes utilisés pour entraîner les données (réduit le surapprentissage mais aussi l'ensemble de données d'entraînement).
|
||
|
||
En outre, notez que la taille du lot dans ce cas est de 2 pour diviser les données en 2 lots, l'objectif principal étant de permettre un traitement parallèle et de réduire la consommation par lot.
|
||
```python
|
||
train_ratio = 0.90
|
||
split_idx = int(train_ratio * len(text_data))
|
||
train_data = text_data[:split_idx]
|
||
val_data = text_data[split_idx:]
|
||
|
||
torch.manual_seed(123)
|
||
|
||
train_loader = create_dataloader_v1(
|
||
train_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=True,
|
||
shuffle=True,
|
||
num_workers=0
|
||
)
|
||
|
||
val_loader = create_dataloader_v1(
|
||
val_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=False,
|
||
shuffle=False,
|
||
num_workers=0
|
||
)
|
||
```
|
||
## Vérifications de cohérence
|
||
|
||
L'objectif est de vérifier qu'il y a suffisamment de tokens pour l'entraînement, que les formes sont celles attendues et d'obtenir des informations sur le nombre de tokens utilisés pour l'entraînement et pour la validation :
|
||
```python
|
||
# Sanity checks
|
||
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the training loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"increase the `training_ratio`")
|
||
|
||
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the validation loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"decrease the `training_ratio`")
|
||
|
||
print("Train loader:")
|
||
for x, y in train_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
print("\nValidation loader:")
|
||
for x, y in val_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
train_tokens = 0
|
||
for input_batch, target_batch in train_loader:
|
||
train_tokens += input_batch.numel()
|
||
|
||
val_tokens = 0
|
||
for input_batch, target_batch in val_loader:
|
||
val_tokens += input_batch.numel()
|
||
|
||
print("Training tokens:", train_tokens)
|
||
print("Validation tokens:", val_tokens)
|
||
print("All tokens:", train_tokens + val_tokens)
|
||
```
|
||
### Sélectionner le dispositif pour l'entraînement et les pré-calculs
|
||
|
||
Le code suivant sélectionne simplement le dispositif à utiliser et calcule une perte d'entraînement et une perte de validation (sans avoir encore entraîné quoi que ce soit) comme point de départ.
|
||
```python
|
||
# Indicate the device to use
|
||
|
||
if torch.cuda.is_available():
|
||
device = torch.device("cuda")
|
||
elif torch.backends.mps.is_available():
|
||
device = torch.device("mps")
|
||
else:
|
||
device = torch.device("cpu")
|
||
|
||
print(f"Using {device} device.")
|
||
|
||
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
|
||
|
||
# Pre-calculate losses without starting yet
|
||
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
|
||
|
||
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
|
||
train_loss = calc_loss_loader(train_loader, model, device)
|
||
val_loss = calc_loss_loader(val_loader, model, device)
|
||
|
||
print("Training loss:", train_loss)
|
||
print("Validation loss:", val_loss)
|
||
```
|
||
### Fonctions d'entraînement
|
||
|
||
La fonction `generate_and_print_sample` va simplement obtenir un contexte et générer quelques tokens afin d'avoir une idée de la performance du modèle à ce moment-là. Cela est appelé par `train_model_simple` à chaque étape.
|
||
|
||
La fonction `evaluate_model` est appelée aussi souvent que l'indique la fonction d'entraînement et elle est utilisée pour mesurer la perte d'entraînement et la perte de validation à ce moment dans l'entraînement du modèle.
|
||
|
||
Ensuite, la grande fonction `train_model_simple` est celle qui entraîne réellement le modèle. Elle attend :
|
||
|
||
- Le chargeur de données d'entraînement (avec les données déjà séparées et préparées pour l'entraînement)
|
||
- Le chargeur de validation
|
||
- L'**optimiseur** à utiliser pendant l'entraînement : C'est la fonction qui utilisera les gradients et mettra à jour les paramètres pour réduire la perte. Dans ce cas, comme vous le verrez, `AdamW` est utilisé, mais il en existe beaucoup d'autres.
|
||
- `optimizer.zero_grad()` est appelé pour réinitialiser les gradients à chaque tour afin de ne pas les accumuler.
|
||
- Le paramètre **`lr`** est le **taux d'apprentissage** qui détermine la **taille des étapes** prises pendant le processus d'optimisation lors de la mise à jour des paramètres du modèle. Un **taux d'apprentissage** **plus petit** signifie que l'optimiseur **effectue des mises à jour plus petites** des poids, ce qui peut conduire à une convergence plus **précise** mais peut **ralentir** l'entraînement. Un **taux d'apprentissage** **plus grand** peut accélérer l'entraînement mais **risque de dépasser** le minimum de la fonction de perte (**sauter par-dessus** le point où la fonction de perte est minimisée).
|
||
- La **décroissance de poids** modifie l'étape de **calcul de la perte** en ajoutant un terme supplémentaire qui pénalise les poids importants. Cela encourage l'optimiseur à trouver des solutions avec des poids plus petits, équilibrant entre un bon ajustement des données et le maintien d'un modèle simple, prévenant le surajustement dans les modèles d'apprentissage automatique en décourageant le modèle d'accorder trop d'importance à une seule caractéristique.
|
||
- Les optimisateurs traditionnels comme SGD avec régularisation L2 couplent la décroissance de poids avec le gradient de la fonction de perte. Cependant, **AdamW** (une variante de l'optimiseur Adam) découple la décroissance de poids de la mise à jour du gradient, conduisant à une régularisation plus efficace.
|
||
- Le dispositif à utiliser pour l'entraînement
|
||
- Le nombre d'époques : Nombre de fois à passer sur les données d'entraînement
|
||
- La fréquence d'évaluation : La fréquence d'appel de `evaluate_model`
|
||
- L'itération d'évaluation : Le nombre de lots à utiliser lors de l'évaluation de l'état actuel du modèle lors de l'appel de `generate_and_print_sample`
|
||
- Le contexte de départ : Quelle phrase de départ utiliser lors de l'appel de `generate_and_print_sample`
|
||
- Le tokenizer
|
||
```python
|
||
# Functions to train the data
|
||
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
|
||
eval_freq, eval_iter, start_context, tokenizer):
|
||
# Initialize lists to track losses and tokens seen
|
||
train_losses, val_losses, track_tokens_seen = [], [], []
|
||
tokens_seen, global_step = 0, -1
|
||
|
||
# Main training loop
|
||
for epoch in range(num_epochs):
|
||
model.train() # Set model to training mode
|
||
|
||
for input_batch, target_batch in train_loader:
|
||
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
loss.backward() # Calculate loss gradients
|
||
optimizer.step() # Update model weights using loss gradients
|
||
tokens_seen += input_batch.numel()
|
||
global_step += 1
|
||
|
||
# Optional evaluation step
|
||
if global_step % eval_freq == 0:
|
||
train_loss, val_loss = evaluate_model(
|
||
model, train_loader, val_loader, device, eval_iter)
|
||
train_losses.append(train_loss)
|
||
val_losses.append(val_loss)
|
||
track_tokens_seen.append(tokens_seen)
|
||
print(f"Ep {epoch+1} (Step {global_step:06d}): "
|
||
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
|
||
|
||
# Print a sample text after each epoch
|
||
generate_and_print_sample(
|
||
model, tokenizer, device, start_context
|
||
)
|
||
|
||
return train_losses, val_losses, track_tokens_seen
|
||
|
||
|
||
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
|
||
model.eval() # Set in eval mode to avoid dropout
|
||
with torch.no_grad():
|
||
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
|
||
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
|
||
model.train() # Back to training model applying all the configurations
|
||
return train_loss, val_loss
|
||
|
||
|
||
def generate_and_print_sample(model, tokenizer, device, start_context):
|
||
model.eval() # Set in eval mode to avoid dropout
|
||
context_size = model.pos_emb.weight.shape[0]
|
||
encoded = text_to_token_ids(start_context, tokenizer).to(device)
|
||
with torch.no_grad():
|
||
token_ids = generate_text(
|
||
model=model, idx=encoded,
|
||
max_new_tokens=50, context_size=context_size
|
||
)
|
||
decoded_text = token_ids_to_text(token_ids, tokenizer)
|
||
print(decoded_text.replace("\n", " ")) # Compact print format
|
||
model.train() # Back to training model applying all the configurations
|
||
```
|
||
> [!TIP]
|
||
> Pour améliorer le taux d'apprentissage, il existe quelques techniques pertinentes appelées **linear warmup** et **cosine decay.**
|
||
>
|
||
> **Linear warmup** consiste à définir un taux d'apprentissage initial et un maximum, puis à le mettre à jour de manière cohérente après chaque époque. Cela est dû au fait que commencer l'entraînement avec des mises à jour de poids plus petites diminue le risque que le modèle rencontre des mises à jour importantes et déstabilisantes pendant sa phase d'entraînement.\
|
||
> **Cosine decay** est une technique qui **réduit progressivement le taux d'apprentissage** suivant une courbe demi-cosinus **après la phase de warmup**, ralentissant les mises à jour de poids pour **minimiser le risque de dépasser** les minima de perte et garantir la stabilité de l'entraînement dans les phases ultérieures.
|
||
>
|
||
> _Notez que ces améliorations ne sont pas incluses dans le code précédent._
|
||
|
||
### Start training
|
||
```python
|
||
import time
|
||
start_time = time.time()
|
||
|
||
torch.manual_seed(123)
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.to(device)
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
|
||
|
||
num_epochs = 10
|
||
train_losses, val_losses, tokens_seen = train_model_simple(
|
||
model, train_loader, val_loader, optimizer, device,
|
||
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
|
||
start_context="Every effort moves you", tokenizer=tokenizer
|
||
)
|
||
|
||
end_time = time.time()
|
||
execution_time_minutes = (end_time - start_time) / 60
|
||
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
|
||
```
|
||
### Imprimer l'évolution de l'entraînement
|
||
|
||
Avec la fonction suivante, il est possible d'imprimer l'évolution du modèle pendant qu'il était en cours d'entraînement.
|
||
```python
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.ticker import MaxNLocator
|
||
import math
|
||
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
|
||
fig, ax1 = plt.subplots(figsize=(5, 3))
|
||
ax1.plot(epochs_seen, train_losses, label="Training loss")
|
||
ax1.plot(
|
||
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
|
||
)
|
||
ax1.set_xlabel("Epochs")
|
||
ax1.set_ylabel("Loss")
|
||
ax1.legend(loc="upper right")
|
||
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
|
||
ax2 = ax1.twiny()
|
||
ax2.plot(tokens_seen, train_losses, alpha=0)
|
||
ax2.set_xlabel("Tokens seen")
|
||
fig.tight_layout()
|
||
plt.show()
|
||
|
||
# Compute perplexity from the loss values
|
||
train_ppls = [math.exp(loss) for loss in train_losses]
|
||
val_ppls = [math.exp(loss) for loss in val_losses]
|
||
# Plot perplexity over tokens seen
|
||
plt.figure()
|
||
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
|
||
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
|
||
plt.xlabel('Tokens Seen')
|
||
plt.ylabel('Perplexity')
|
||
plt.title('Perplexity over Training')
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
|
||
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
|
||
```
|
||
### Enregistrer le modèle
|
||
|
||
Il est possible d'enregistrer le modèle + l'optimiseur si vous souhaitez continuer l'entraînement plus tard :
|
||
```python
|
||
# Save the model and the optimizer for later training
|
||
torch.save({
|
||
"model_state_dict": model.state_dict(),
|
||
"optimizer_state_dict": optimizer.state_dict(),
|
||
},
|
||
"/tmp/model_and_optimizer.pth"
|
||
)
|
||
# Note that this model with the optimizer occupied close to 2GB
|
||
|
||
# Restore model and optimizer for training
|
||
checkpoint = torch.load("/tmp/model_and_optimizer.pth", map_location=device)
|
||
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.load_state_dict(checkpoint["model_state_dict"])
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=0.1)
|
||
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
|
||
model.train(); # Put in training mode
|
||
```
|
||
Ou juste le modèle si vous prévoyez de l'utiliser uniquement :
|
||
```python
|
||
# Save the model
|
||
torch.save(model.state_dict(), "model.pth")
|
||
|
||
# Load it
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
|
||
model.load_state_dict(torch.load("model.pth", map_location=device))
|
||
|
||
model.eval() # Put in eval mode
|
||
```
|
||
## Chargement des poids GPT2
|
||
|
||
Il y a 2 scripts rapides pour charger les poids GPT2 localement. Pour les deux, vous pouvez cloner le dépôt [https://github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) localement, puis :
|
||
|
||
- Le script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py) téléchargera tous les poids et transformera les formats d'OpenAI vers ceux attendus par notre LLM. Le script est également préparé avec la configuration nécessaire et avec le prompt : "Every effort moves you"
|
||
- Le script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb) vous permet de charger n'importe lequel des poids GPT2 localement (il suffit de changer la variable `CHOOSE_MODEL`) et de prédire du texte à partir de certains prompts.
|
||
|
||
## Références
|
||
|
||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
||
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|