6. प्री-ट्रेनिंग और मॉडल लोड करना

tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें

टेक्स्ट जनरेशन

एक मॉडल को प्रशिक्षित करने के लिए हमें उस मॉडल को नए टोकन उत्पन्न करने में सक्षम होना चाहिए। फिर हम उत्पन्न टोकनों की तुलना अपेक्षित टोकनों से करेंगे ताकि मॉडल को उन टोकनों को सीखने के लिए प्रशिक्षित किया जा सके जिन्हें उसे उत्पन्न करने की आवश्यकता है

जैसे कि पिछले उदाहरणों में हमने कुछ टोकनों की भविष्यवाणी की थी, इसे इस उद्देश्य के लिए पुन: उपयोग करना संभव है।

tip

इस छठे चरण का लक्ष्य बहुत सरल है: मॉडल को शून्य से प्रशिक्षित करना। इसके लिए पिछले LLM आर्किटेक्चर का उपयोग किया जाएगा जिसमें डेटा सेट पर परिभाषित हानि कार्यों और ऑप्टिमाइज़र का उपयोग करते हुए कुछ लूप होंगे ताकि मॉडल के सभी पैरामीटर को प्रशिक्षित किया जा सके।

टेक्स्ट मूल्यांकन

सही प्रशिक्षण करने के लिए अपेक्षित टोकन के लिए प्राप्त भविष्यवाणियों को मापना आवश्यक है। प्रशिक्षण का लक्ष्य सही टोकन की संभावना को अधिकतम करना है, जिसमें अन्य टोकनों की तुलना में इसकी संभावना को बढ़ाना शामिल है।

सही टोकन की संभावना को अधिकतम करने के लिए, मॉडल के वेट्स को इस प्रकार संशोधित किया जाना चाहिए कि संभावना अधिकतम हो। वेट्स के अपडेट बैकप्रोपेगेशन के माध्यम से किए जाते हैं। इसके लिए एक हानि कार्य की आवश्यकता होती है जिसे अधिकतम करना है। इस मामले में, कार्य होगा किए गए भविष्यवाणी और इच्छित एक के बीच का अंतर

हालांकि, कच्ची भविष्यवाणियों के साथ काम करने के बजाय, यह आधार n के साथ एक लॉगरिदम के साथ काम करेगा। इसलिए यदि अपेक्षित टोकन की वर्तमान भविष्यवाणी 7.4541e-05 थी, तो 7.4541e-05 का प्राकृतिक लॉगरिदम (आधार e) लगभग -9.5042 है।
फिर, उदाहरण के लिए, 5 टोकनों की संदर्भ लंबाई के साथ प्रत्येक प्रविष्टि के लिए, मॉडल को 5 टोकनों की भविष्यवाणी करने की आवश्यकता होगी, पहले 4 टोकन इनपुट के अंतिम होंगे और पांचवां भविष्यवाणी किया गया होगा। इसलिए, प्रत्येक प्रविष्टि के लिए हमारे पास उस मामले में 5 भविष्यवाणियाँ होंगी (हालांकि पहले 4 इनपुट में थे, मॉडल इसे नहीं जानता) जिसमें 5 अपेक्षित टोकन और इसलिए 5 संभावनाएँ अधिकतम करने के लिए होंगी।

इसलिए, प्रत्येक भविष्यवाणी के लिए प्राकृतिक लॉगरिदम करने के बाद, औसत की गणना की जाती है, माइनस प्रतीक हटा दिया जाता है (इसे क्रॉस एंट्रॉपी लॉस कहा जाता है) और यही संख्या है जिसे 0 के करीब लाना है क्योंकि 1 का प्राकृतिक लॉगरिदम 0 है:

https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233

मॉडल की गुणवत्ता को मापने का एक और तरीका परप्लेक्सिटी कहा जाता है। परप्लेक्सिटी एक मीट्रिक है जिसका उपयोग यह मूल्यांकन करने के लिए किया जाता है कि एक संभावना मॉडल एक नमूने की भविष्यवाणी कितनी अच्छी तरह करता है। भाषा मॉडलिंग में, यह अनुक्रम में अगले टोकन की भविष्यवाणी करते समय मॉडल की अनिश्चितता का प्रतिनिधित्व करता है।
उदाहरण के लिए, 48725 का परप्लेक्सिटी मान, इसका मतलब है कि जब एक टोकन की भविष्यवाणी करने की आवश्यकता होती है, तो यह यह सुनिश्चित नहीं है कि शब्दकोश में 48,725 टोकनों में से कौन सा सही है।

प्री-ट्रेन उदाहरण

यह प्रारंभिक कोड है जो https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb में प्रस्तावित किया गया है, कभी-कभी थोड़ा संशोधित

यहां उपयोग किया गया पिछला कोड लेकिन पहले के अनुभागों में पहले ही समझाया गया है
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
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"
)

चलिए एक कदम दर कदम व्याख्या देखते हैं

Functions to transform text <--> ids

ये कुछ सरल फ़ंक्शन हैं जो शब्दावली से टेक्स्ट को आईडी में और इसके विपरीत परिवर्तित करने के लिए उपयोग किए जा सकते हैं। यह टेक्स्ट के प्रबंधन की शुरुआत में और भविष्यवाणियों के अंत में आवश्यक है:

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())

Generate text functions

पिछले अनुभाग में एक फ़ंक्शन था जो सबसे संभावित टोकन को लॉजिट प्राप्त करने के बाद प्राप्त करता था। हालाँकि, इसका मतलब यह होगा कि प्रत्येक प्रविष्टि के लिए हमेशा एक ही आउटपुट उत्पन्न होगा, जो इसे बहुत निश्चित बनाता है।

निम्नलिखित generate_text फ़ंक्शन, top-k, temperature और multinomial अवधारणाओं को लागू करेगा।

  • top-k का अर्थ है कि हम शीर्ष k टोकनों को छोड़कर सभी टोकनों की संभावनाओं को -inf तक कम करना शुरू करेंगे। इसलिए, यदि k=3 है, तो निर्णय लेने से पहले केवल 3 सबसे संभावित टोकनों की संभावना -inf से अलग होगी।
  • temperature का अर्थ है कि प्रत्येक संभावना को तापमान मान से विभाजित किया जाएगा। 0.1 का मान उच्चतम संभावना को सबसे कम संभावना की तुलना में बेहतर बनाएगा, जबकि उदाहरण के लिए 5 का तापमान इसे अधिक समतल बना देगा। यह LLM के उत्तरों में विविधता को सुधारने में मदद करता है।
  • तापमान लागू करने के बाद, एक softmax फ़ंक्शन फिर से लागू किया जाता है ताकि सभी शेष टोकनों की कुल संभावना 1 हो।
  • अंत में, सबसे बड़ी संभावना वाले टोकन को चुनने के बजाय, फ़ंक्शन multinomial को लागू किया जाता है ताकि अंतिम संभावनाओं के अनुसार अगले टोकन की भविष्यवाणी की जा सके। इसलिए यदि टोकन 1 की संभावनाएँ 70% थीं, टोकन 2 की 20% और टोकन 3 की 10%, तो 70% समय टोकन 1 का चयन किया जाएगा, 20% समय यह टोकन 2 होगा और 10% समय यह टोकन 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

एक सामान्य विकल्प है top-k जिसे top-p कहा जाता है, जिसे नाभिक नमूना भी कहा जाता है, जो सबसे अधिक संभावना वाले k नमूनों को प्राप्त करने के बजाय, सभी परिणामस्वरूप शब्दावली को संभावनाओं के अनुसार व्यवस्थित करता है और सबसे उच्च संभावना से सबसे कम तक योग करता है जब तक कि एक थ्रेशोल्ड तक नहीं पहुँचता

फिर, केवल उन शब्दों को उनकी सापेक्ष संभावनाओं के अनुसार विचार किया जाएगा।

यह k नमूनों की संख्या का चयन करने की आवश्यकता को समाप्त करता है, क्योंकि प्रत्येक मामले में आदर्श k भिन्न हो सकता है, बल्कि केवल एक थ्रेशोल्ड

ध्यान दें कि यह सुधार पिछले कोड में शामिल नहीं है।

tip

उत्पन्न पाठ को सुधारने का एक और तरीका है Beam search का उपयोग करना, बजाय इस उदाहरण में उपयोग किए गए लालची खोज के।
लालची खोज के विपरीत, जो प्रत्येक चरण में सबसे संभावित अगले शब्द का चयन करता है और एकल अनुक्रम बनाता है, बीम खोज प्रत्येक चरण में शीर्ष 𝑘 k उच्चतम-स्कोरिंग आंशिक अनुक्रमों (जिसे "बीम" कहा जाता है) का ट्रैक रखता है। कई संभावनाओं का एक साथ अन्वेषण करके, यह दक्षता और गुणवत्ता के बीच संतुलन बनाता है, एक बेहतर समग्र अनुक्रम खोजने के अवसरों को बढ़ाता है जो लालची दृष्टिकोण द्वारा जल्दी, उप-आदर्श विकल्पों के कारण छूट सकता है।

ध्यान दें कि यह सुधार पिछले कोड में शामिल नहीं है।

Loss functions

calc_loss_batch फ़ंक्शन एक एकल बैच की भविष्यवाणी की क्रॉस एंट्रोपी की गणना करता है।
calc_loss_loader सभी बैचों की क्रॉस एंट्रोपी प्राप्त करता है और औसत क्रॉस एंट्रोपी की गणना करता है।

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

ग्रेडिएंट क्लिपिंग एक तकनीक है जिसका उपयोग बड़े न्यूरल नेटवर्क में प्रशिक्षण स्थिरता को बढ़ाने के लिए किया जाता है, जिसमें ग्रेडिएंट के परिमाण के लिए एक अधिकतम सीमा निर्धारित की जाती है। जब ग्रेडिएंट इस पूर्वनिर्धारित max_norm को पार कर जाते हैं, तो उन्हें अनुपात में कम किया जाता है ताकि मॉडल के पैरामीटर में अपडेट एक प्रबंधनीय सीमा के भीतर रहें, जिससे विस्फोटक ग्रेडिएंट जैसी समस्याओं से बचा जा सके और अधिक नियंत्रित और स्थिर प्रशिक्षण सुनिश्चित हो सके।

ध्यान दें कि यह सुधार पिछले कोड में शामिल नहीं है।

निम्नलिखित उदाहरण देखें:

डेटा लोड करना

फंक्शंस create_dataloader_v1 और create_dataloader_v1 पहले के अनुभाग में पहले ही चर्चा की जा चुकी हैं।

यहां से ध्यान दें कि 90% टेक्स्ट का उपयोग प्रशिक्षण के लिए किया जाएगा जबकि 10% का उपयोग मान्यता के लिए किया जाएगा और दोनों सेट 2 अलग-अलग डेटा लोडर्स में संग्रहीत हैं।
ध्यान दें कि कभी-कभी डेटा सेट का एक भाग परीक्षण सेट के लिए भी छोड़ा जाता है ताकि मॉडल के प्रदर्शन का बेहतर मूल्यांकन किया जा सके।

दोनों डेटा लोडर्स समान बैच आकार, अधिकतम लंबाई और स्ट्राइड और कार्यकर्ताओं की संख्या (इस मामले में 0) का उपयोग कर रहे हैं।
मुख्य अंतर यह है कि प्रत्येक द्वारा उपयोग किया जाने वाला डेटा और मान्यकर्ता अंतिम को नहीं छोड़ रहा है और डेटा को शफल नहीं कर रहा है क्योंकि यह मान्यता के उद्देश्यों के लिए आवश्यक नहीं है।

इसके अलावा, तथ्य यह है कि स्ट्राइड संदर्भ लंबाई के बराबर है, इसका मतलब है कि डेटा को प्रशिक्षित करने के लिए उपयोग किए गए संदर्भों के बीच ओवरलैप नहीं होगा (ओवरफिटिंग को कम करता है लेकिन प्रशिक्षण डेटा सेट को भी)।

इसके अलावा, ध्यान दें कि इस मामले में बैच आकार 2 है ताकि डेटा को 2 बैच में विभाजित किया जा सके, इसका मुख्य लक्ष्य समानांतर प्रसंस्करण की अनुमति देना और प्रति बैच खपत को कम करना है।

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
)

Sanity Checks

उद्देश्य यह है कि यह जांचा जाए कि प्रशिक्षण के लिए पर्याप्त टोकन हैं, आकार अपेक्षित हैं और प्रशिक्षण और मान्यता के लिए उपयोग किए गए टोकनों की संख्या के बारे में कुछ जानकारी प्राप्त की जाए:

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)

प्रशिक्षण और पूर्व गणनाओं के लिए डिवाइस का चयन करें

The following code just select the device to use and calculates a training loss and validation loss (without having trained anything yet) as a starting point.

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)

प्रशिक्षण कार्य

कार्य generate_and_print_sample केवल एक संदर्भ प्राप्त करेगा और कुछ टोकन उत्पन्न करेगा ताकि यह महसूस किया जा सके कि उस समय मॉडल कितना अच्छा है। इसे train_model_simple द्वारा प्रत्येक चरण पर कॉल किया जाता है।

कार्य evaluate_model को प्रशिक्षण कार्य के अनुसार बार-बार कॉल किया जाता है और इसका उपयोग उस समय मॉडल प्रशिक्षण में ट्रेन लॉस और वैलिडेशन लॉस को मापने के लिए किया जाता है।

फिर बड़ा कार्य train_model_simple है जो वास्तव में मॉडल को प्रशिक्षित करता है। यह अपेक्षा करता है:

  • ट्रेन डेटा लोडर (जिसमें डेटा पहले से अलग और प्रशिक्षण के लिए तैयार किया गया है)
  • वैलिडेटर लोडर
  • प्रशिक्षण के दौरान उपयोग करने के लिए ऑप्टिमाइज़र: यह वह कार्य है जो ग्रेडिएंट्स का उपयोग करेगा और लॉस को कम करने के लिए पैरामीटर को अपडेट करेगा। इस मामले में, जैसा कि आप देखेंगे, AdamW का उपयोग किया गया है, लेकिन और भी कई हैं।
  • optimizer.zero_grad() को प्रत्येक राउंड में ग्रेडिएंट्स को रीसेट करने के लिए कॉल किया जाता है ताकि उन्हें जमा न किया जा सके।
  • lr पैरामीटर लर्निंग रेट है जो ऑप्टिमाइजेशन प्रक्रिया के दौरान मॉडल के पैरामीटर को अपडेट करते समय चरणों के आकार को निर्धारित करता है। एक छोटा लर्निंग रेट ऑप्टिमाइज़र को छोटे अपडेट करने का मतलब है, जो अधिक सटीक समागम की ओर ले जा सकता है लेकिन प्रशिक्षण को धीमा कर सकता है। एक बड़ा लर्निंग रेट प्रशिक्षण को तेज कर सकता है लेकिन लॉस फ़ंक्शन के न्यूनतम को ओवरशूट करने का जोखिम उठाता है (उस बिंदु पर कूदना जहां लॉस फ़ंक्शन न्यूनतम होता है)।
  • वेट डिके लॉस कैलकुलेशन चरण को संशोधित करता है एक अतिरिक्त टर्म जोड़कर जो बड़े वेट्स को दंडित करता है। यह ऑप्टिमाइज़र को छोटे वेट्स के साथ समाधान खोजने के लिए प्रोत्साहित करता है, डेटा को अच्छी तरह से फिट करने और मॉडल को सरल रखने के बीच संतुलन बनाते हुए मशीन लर्निंग मॉडलों में ओवरफिटिंग को रोकने के लिए किसी एक विशेषता को अधिक महत्व देने से हतोत्साहित करता है।
  • पारंपरिक ऑप्टिमाइज़र जैसे SGD के साथ L2 नियमितीकरण वेट डिके को लॉस फ़ंक्शन के ग्रेडिएंट के साथ जोड़ते हैं। हालाँकि, AdamW (एडम ऑप्टिमाइज़र का एक रूप) वेट डिके को ग्रेडिएंट अपडेट से अलग करता है, जिससे अधिक प्रभावी नियमितीकरण होता है।
  • प्रशिक्षण के लिए उपयोग करने के लिए डिवाइस
  • एपॉक्स की संख्या: प्रशिक्षण डेटा पर जाने की次数
  • मूल्यांकन आवृत्ति: evaluate_model को कॉल करने की आवृत्ति
  • मूल्यांकन पुनरावृत्ति: वर्तमान स्थिति का मूल्यांकन करते समय generate_and_print_sample को कॉल करते समय उपयोग करने के लिए बैचों की संख्या
  • प्रारंभ संदर्भ: generate_and_print_sample को कॉल करते समय उपयोग करने के लिए प्रारंभिक वाक्य
  • टोकनाइज़र
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

सीखने की दर में सुधार करने के लिए कुछ प्रासंगिक तकनीकें हैं जिन्हें linear warmup और cosine decay कहा जाता है।

Linear warmup में एक प्रारंभिक सीखने की दर और एक अधिकतम दर को परिभाषित करना शामिल है और प्रत्येक युग के बाद इसे लगातार अपडेट करना शामिल है। इसका कारण यह है कि छोटे वजन अपडेट के साथ प्रशिक्षण शुरू करने से मॉडल के बड़े, अस्थिर अपडेट का सामना करने का जोखिम कम हो जाता है।
Cosine decay एक तकनीक है जो warmup चरण के बाद आधा-कोसाइन वक्र का पालन करते हुए सीखने की दर को धीरे-धीरे कम करती है, वजन अपडेट को धीमा करके हानि के न्यूनतम स्तर को पार करने के जोखिम को कम करने और बाद के चरणों में प्रशिक्षण की स्थिरता सुनिश्चित करती है।

ध्यान दें कि ये सुधार पिछले कोड में शामिल नहीं हैं।

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.")

प्रिंट प्रशिक्षण विकास

इस फ़ंक्शन के साथ, यह संभव है कि मॉडल के प्रशिक्षण के दौरान विकास को प्रिंट किया जा सके।

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)

मॉडल को सहेजें

यदि आप बाद में प्रशिक्षण जारी रखना चाहते हैं तो मॉडल + ऑप्टिमाइज़र को सहेजना संभव है:

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

या केवल मॉडल यदि आप केवल इसका उपयोग करने की योजना बना रहे हैं:

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

GPT2 वेट्स लोड करना

GPT2 वेट्स को स्थानीय रूप से लोड करने के लिए 2 त्वरित स्क्रिप्ट हैं। दोनों के लिए आप स्थानीय रूप से रिपॉजिटरी को क्लोन कर सकते हैं https://github.com/rasbt/LLMs-from-scratch, फिर:

संदर्भ

tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें