From 30870ccce08dbc48f16df03b32042795e8d72065 Mon Sep 17 00:00:00 2001 From: skindhu Date: Tue, 5 Nov 2024 17:15:09 +0800 Subject: [PATCH] add fourth chapter --- ...从零开始实现一个用于文本生成的 GPT 模型.md | 96 +++++++++++-------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/cn-Book/4.从零开始实现一个用于文本生成的 GPT 模型.md b/cn-Book/4.从零开始实现一个用于文本生成的 GPT 模型.md index 665842f..3ab121a 100644 --- a/cn-Book/4.从零开始实现一个用于文本生成的 GPT 模型.md +++ b/cn-Book/4.从零开始实现一个用于文本生成的 GPT 模型.md @@ -6,15 +6,27 @@ + **通过实现 Transformer 模块来构建不同规模的 GPT 模型** + **计算 GPT 模型的参数数量和存储需求** - +----- + +- [4.1 实现 LLM 的架构](#41-实现-llm-的架构) +- [4.2 使用层归一化对激活值进行标准化](#42-使用层归一化对激活值进行标准化) +- [4.3 实现带有 GELU 激活函数的前馈神经网络](#43-实现带有-gelu-激活函数的前馈神经网络) +- [4.4 添加快捷连接](#44-添加快捷连接) +- [4.5 在 Transformer 模块中连接注意力层与线性层](#45-在-transformer-模块中连接注意力层与线性层) +- [4.6 实现 GPT 模型](#46-实现-gpt-模型) +- [4.7 生成文本](#47-生成文本) +- [4.8 总结](#48-总结) + +----- + 在上一章中,我们学习并实现了多头注意力机制,这是大语言模型(LLM)的核心组件之一。本章将进一步实现 LLM 的其他组件,并将它们组装成一个与 GPT 类似结构的模型。我们将在下一章中训练该模型,以生成类人文本,具体过程如图 4.1 所示。 -大语言模型(LLM)架构(见图 4.1)由多个模块构成,我们将在本章中实现这些模块。接下来的内容,我们首先从整体视角介绍模型架构,然后详细讲解各个组件。 +大语言模型(LLM)架构(见图 4.1)由多个模块构成,我们将在本章中实现这些模块。接下来的内容,我们首先从整体视角介绍模型架构,然后详细讲解各个组件。 + - ## 4.1 实现 LLM 的架构 @@ -40,12 +52,12 @@ LLM(如GPT,即生成式预训练 Transformer,Generative Pretrained Transfo ```python GPT_CONFIG_124M = { - "vocab_size": 50257, # Vocabulary size + "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 + "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 } ``` @@ -82,7 +94,7 @@ class DummyGPTModel(nn.Module): 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) @@ -93,22 +105,22 @@ class DummyGPTModel(nn.Module): x = self.final_norm(x) logits = self.out_head(x) return logits - + class DummyTransformerBlock(nn.Module): #C def __init__(self, cfg): super().__init__() - + def forward(self, x): #D return x class DummyLayerNorm(nn.Module): #E def __init__(self, normalized_shape, eps=1e-5): #F super().__init__() - + def forward(self, x): return x - - + + #A 为 TransformerBlock 设置占位符 #B 为 LayerNorm 设置占位符 #C 一个简单的占位类,后续将被真正的 TransformerBlock 替换 @@ -148,7 +160,7 @@ print(batch) ```python tensor([[ 6109, 3626, 6100, 345], #A [ 6109, 1110, 6622, 257]]) - + #A 第一行对应第一段文本,第二行对应第二段文本。 ``` @@ -170,7 +182,7 @@ tensor([[[-1.2034, 0.3201, -0.7130, ..., -1.5548, -0.2390, -0.4667], [-0.1192, 0.4539, -0.4432, ..., 0.2392, 1.3469, 1.2430], [ 0.5307, 1.6720, -0.4695, ..., 1.1966, 0.0111, 0.5835], [ 0.0139, 1.6755, -0.3388, ..., 1.1586, -0.0435, -1.0400]], - + [[-1.0908, 0.1798, -0.9484, ..., -1.6047, 0.2439, -0.4530], [-0.7860, 0.5581, -0.0610, ..., 0.4835, -0.0077, 1.6621], [ 0.3567, 1.2698, -0.6398, ..., -0.0162, -0.1296, 0.3717], @@ -184,7 +196,7 @@ tensor([[[-1.2034, 0.3201, -0.7130, ..., -1.5548, -0.2390, -0.4667], 在对 GPT 架构及其输入输出进行了大概介绍之后,接下来的章节中将编写各个占位模块的实现,首先从用真实的层归一化类替换之前代码中的 DummyLayerNorm 开始。 - + ## 4.2 使用层归一化对激活值进行标准化 @@ -253,7 +265,7 @@ print("Variance:\n", var) Mean: tensor([[0.1324], [0.2170]], grad_fn=) - + Variance: tensor([[0.0231], [0.0398]], grad_fn=) @@ -287,11 +299,11 @@ Normalized layer outputs: tensor([[ 0.6159, 1.4126, -0.8719, 0.5872, -0.8719, -0.8719], [-0.0189, 0.1121, -1.0876, 1.5173, 0.5647, -1.0876]], grad_fn=) - + Mean: tensor([[2.9802e-08], [3.9736e-08]], grad_fn=) - + Variance: tensor([[1.], [1.]], grad_fn=) @@ -373,7 +385,7 @@ Variance: > > 如果你熟悉批量归一化这种常见的传统神经网络归一化方法,可能会好奇它与层归一化的区别。与在数量维度上进行归一化的批量归一化不同,层归一化是在特征维度上进行归一化。LLM 通常需要大量计算资源,而可用的硬件资源或特定的使用场景可能会限制训练或推理过程中的批量大小。由于层归一化对每个输入的处理不依赖批量大小,因此在这些场景下提供了更高的灵活性和稳定性。这对于分布式训练或资源受限的环境中部署模型尤其有利。 - + ## 4.3 实现带有 GELU 激活函数的前馈神经网络 @@ -394,7 +406,7 @@ $$ \text{GELU}(x) \approx 0.5 \cdot x \cdot\left(1+\tanh \left[\sqrt{(2 / \pi)} 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)) * @@ -494,7 +506,7 @@ torch.Size([2, 3, 768]) 下一节,我们将介绍“快捷连接”的概念,即在神经网络的不同层之间插入的连接结构,它对于提升深度神经网络架构的训练性能非常重要。 - + ## 4.4 添加快捷连接 @@ -553,15 +565,15 @@ def print_gradients(model, x): # Forward pass output = model(x) target = torch.tensor([[0.]]) - + # Calculate loss based on how close the target # and output are loss = nn.MSELoss() loss = loss(output, target) - + # Backward pass to calculate the gradients loss.backward() - + for name, param in model.named_parameters(): if 'weight' in name: # Print the mean absolute gradient of the weights @@ -673,7 +685,7 @@ layers.4.0.weight has gradient mean of 1.3258541822433472 > > 这样,即使 $` \frac{\partial F\left(X_{1}\right)}{\partial X_{1}} `$ 很小,梯度依然可以通过 111 这条路径直接传递到更前面的层。 - + ## 4.5 在 Transformer 模块中连接注意力层与线性层 @@ -707,7 +719,7 @@ class TransformerBlock(nn.Module): 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 = x #A x = self.norm1(x) @@ -720,8 +732,8 @@ class TransformerBlock(nn.Module): x = self.drop_shortcut(x) x = x + shortcut #C return x - - + + #A 注意力模块中的快捷连接 #B 前馈网络模块中的快捷链接 #C 将原始输入加回到输出中 @@ -764,7 +776,7 @@ Transformer 模块结构中保持数据形状不变并非偶然,而是其设 如图 4.14 所示,Transformer 模块由层归一化、带有 GELU 激活函数的前馈网络和快捷连接组成,这些内容在本章前面已经讨论过。正如我们将在接下来的章节中看到的,这个 Transformer 模块将构成我们要实现的 GPT 架构的核心部分。 - + ## 4.6 实现 GPT 模型 @@ -790,19 +802,19 @@ class GPTModel(nn.Module): 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)) #A x = tok_embeds + pos_embeds x = self.drop_emb(x) @@ -810,7 +822,7 @@ class GPTModel(nn.Module): x = self.final_norm(x) logits = self.out_head(x) return logits - + #A 设备设置将根据输入数据所在的位置选择在 CPU 或 GPU 上训练模型 ``` @@ -936,7 +948,7 @@ Total size of the model: 621.83 MB > > 本章中,我们初始化了一个拥有 1.24 亿参数的 GPT 模型,即 GPT-2 small。请在不更改代码的情况下(仅更新配置文件),使用 `GPTModel` 类实现 GPT-2 medium(1024 维嵌入、24 层 Transformer 块、16 个多头注意力头)、GPT-2 large(1280 维嵌入、36 层 Transformer 块、20 个多头注意力头)和 GPT-2 XL(1600 维嵌入、48 层 Transformer 块、25 个多头注意力头)。作为附加任务,请计算每个 GPT 模型的总参数量。 - + ## 4.7 生成文本 @@ -967,15 +979,15 @@ def generate_text_simple(model, idx, max_new_tokens, context_size): #A idx_cond = idx[:, -context_size:] #B with torch.no_grad(): logits = model(idx_cond) - + logits = logits[:, -1, :] #C probas = torch.softmax(logits, dim=-1) #D idx_next = torch.argmax(probas, dim=-1, keepdim=True) #E idx = torch.cat((idx, idx_next), dim=1) #F - + return idx - - + + #A idx 是当前上下文中索引的数组,形状为 (batch, n_tokens) #B 若上下文长度超出支持范围,则进行裁剪。例如,若模型仅支持 5 个 token,而上下文长度为 10,仅使用最后 5 个 token 作为上下文 #C 仅关注最后一个时间步,将形状从 (batch, n_token, vocab_size) 转换为 (batch, vocab_size) @@ -1061,7 +1073,7 @@ Hello, I am Featureiman Byeswickattribute argue > > 在本章开头,我们在 `GPT_CONFIG_124M` 字典中定义了一个全局的 `"drop_rate"` 设置,用于统一设置整个 GPTModel 架构中各处的 dropout 率。请修改代码,为模型架构中各个不同的 dropout 层指定独立的 dropout 值。(提示:我们在三个不同的地方使用了dropout层:嵌入层、快捷连接层和多头注意力模块) - + ## 4.8 总结