大模型入门 ch04:实现一个GPT模型

news2024/9/21 19:52:13

本文是github上的大模型教程LLMs-from-scratch的学习笔记,教程地址:教程链接

LLM大模型主要是参数量大,而不是代码量大。

这是本节的具体内容

  1. 首先实现一个GPT的骨架
  2. 分别实现GPT骨架内的各个部分,包括LayerNorm,GELU,Feed forward network和Shorcut connections
  3. 将四个部分合成Transformer块
  4. 重复多次Transformer块,实现最终的GPT架构

1. GPT骨架

首先确定模型的配置,包括词汇表数量,上下文长度,embeddding维度等等。

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
}
  • "vocab_size"表示词汇量为50257个单词,由第2章讨论的BPE分词器支持
  • "context_length"表示模型的最大输入标记数,这是由第2章中介绍的位置嵌入启用的
  • "emb_dim"是token输入的嵌入大小,将每个输入token转换为768维向量
  • "n_heads"是第3章实现的多头注意力机制中注意力头的数量
  • "n_layers"是模型中transformer块的数量,我们将在接下来的部分中实现
  • "drop_rate"是第3章中讨论的dropout机制的强度;0.1意味着在训练过程中丢弃10%的隐藏单元以缓解过拟合
  • “qkv_bias”决定多头注意力机制(来自第3章)中的“线性”层在计算查询(Q)、键(K)和值(V)张量时是否应该包含一个偏差向量。

首先我们搭好这个骨架,后续进行补充完善:

import torch
import torch.nn as nn


class DummyGPTModel(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"])
        
        # Use a placeholder for TransformerBlock
        self.trf_blocks = nn.Sequential(
            *[DummyTransformerBlock(cfg) for _ in range(cfg["n_layers"])])
        
        # Use a placeholder for LayerNorm
        self.final_norm = DummyLayerNorm(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
        x = self.drop_emb(x)
        x = self.trf_blocks(x)
        x = self.final_norm(x)
        logits = self.out_head(x)
        return logits


class DummyTransformerBlock(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        # A simple placeholder

    def forward(self, x):
        # This block does nothing and just returns its input.
        return x


class DummyLayerNorm(nn.Module):
    def __init__(self, normalized_shape, eps=1e-5):
        super().__init__()
        # The parameters here are just to mimic the LayerNorm interface.

    def forward(self, x):
        # This layer does nothing and just returns its input.
        return x

2. Layer Normalization

Layer normalization将神经网络的激活值归一化到均值为0,方差为1,使得训练更加稳定和快速。在多头注意力机制的前后,我们都会加入Layernorm层。

归一化可以对不同的维度进行归一化

例如在我们这个例子中,如果对行归一化,那么就是对每一个样本进行归一化,如果是列归一化,就是对每一个特征进行归一化。通过减去平均值再除以均方根,我们可以将输入归一化成均值为0,方差为1。

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
  • 除了通过减去均值并除以方差来执行规范化之外,我们还添加了两个可训练参数,scaleshift参数
  • 初始值scale(乘以1)和shift(加0)没有任何影响;然而,scaleshift是可训练参数,如果确定这样做会提高模型在训练任务上的性能,LLM会在训练过程中自动调整
  • 这允许模型学习最适合其处理数据的适当缩放和平移
  • 在计算方差的平方根之前,我们还添加了一个较小的值(eps);这是为了避免方差为0时除零误差

3. 前馈网络

  • 在本节中,我们实现了一个小的神经网络子模块,该子模块用作LLMs中transformer模块的一部分
  • 在深度学习中,ReLU(整流线性单元)激活函数由于其简单性和在各种神经网络架构中的有效性而被广泛使用
  • 在llm中,除了传统的ReLU之外,还使用了各种其他类型的激活函数;两个值得注意的例子是GELU(高斯误差线性单元)和SwiGLU (Swish-Gated线性单元)。
  • GELU和SwiGLU是更复杂的光滑激活函数,分别包含高斯门控线性单元和sigmoid门控线性单元,为深度学习模型提供了更好的性能,不像ReLU的更简单的分段线性函数层-激活函数 (feed forward network with GELU activations)
  • GELU 可以通过多种方式实现。准确的版本被定义为GELU(x)=x⋅Φ(x),其中Φ(x)是标准高斯分布的累积分布函数。
    -在实践中,通常实现一个计算成本更低的近似: GELU ( x ) ≈ 0.5 ⋅ x ⋅ ( 1 + tanh ⁡ [ 2 π ⋅ ( x + 0.044715 ⋅ x 3 ) ] ) \text{GELU}(x) \approx 0.5 \cdot x \cdot \left(1 + \tanh\left[\sqrt{\frac{2}{\pi}} \cdot \left(x + 0.044715 \cdot x^3\right)\right]\right) GELU(x)0.5x(1+tanh[π2 (x+0.044715x3)])
    (原始的GPT-2模型也是用这个近似进行训练的)
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))
        ))

在这里插入图片描述
于是我们可以组成一个FeedFroward前馈网络层:

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)

4. 残差连接

  • 最初,在计算机视觉深度网络(残差网络)中提出了残差连接,以缓解梯度消失问题
  • 连接为渐变在网络中的流动创建了一条更短的路径
  • 这是通过将一个层的输出添加到后面一层的输出来实现的,通常在中间跳过一个或多个层

5. 连接注意力和全连接层,构成Transform block

  • 在本节中,我们现在将前面的概念结合到的transformer块中
  • transformer模块结合了上一章中的因果多头注意力模块和线性层,即我们在上一节中实现的前馈神经网络
  • 此外,transformer block也使用dropout和shortcut连接
from previous_chapters import MultiHeadAttention


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

6. 编写GPT模型

  • transformer块重复多次;在最小的124M GPT-2模型中,我们重复12次。

我们让cfg["n_layers"] = 12

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

7. 生成文本

GPT模型一次只生成一个token

  • 下面的generate_text_simple函数实现了贪婪解码,这是一种简单快速的生成文本的方法
  • 在贪婪解码中,在每一步,模型选择具有最高概率的单词(或标记)作为它的下一个输出(最高的logit对应最高的概率,因此我们在技术上甚至不需要显式地计算softmax函数)
  • 在下一章中,我们将实现一个更高级的generate_text函数
  • 下图描述了GPT模型在给定输入上下文的情况下如何生成下一个单词标记
def generate_text_simple(model, idx, max_new_tokens, context_size):
    # idx is (batch, n_tokens) array of indices in the current context
    for _ in range(max_new_tokens):
        
        # Crop current context if it exceeds the supported context size
        # E.g., if LLM supports only 5 tokens, and the context size is 10
        # then only the last 5 tokens are used as context
        idx_cond = idx[:, -context_size:]
        
        # Get the predictions
        with torch.no_grad():
            logits = model(idx_cond)
        
        # Focus only on the last time step
        # (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
        logits = logits[:, -1, :]  

        # Apply softmax to get probabilities
        probas = torch.softmax(logits, dim=-1)  # (batch, vocab_size)

        # Get the idx of the vocab entry with the highest probability value
        idx_next = torch.argmax(probas, dim=-1, keepdim=True)  # (batch, 1)

        # Append sampled index to the running sequence
        idx = torch.cat((idx, idx_next), dim=1)  # (batch, n_tokens+1)

    return idx

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2153165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shopro前端 短信登录只显示模板不能正常切换

删掉 换成下面的代码 // 打开授权弹框 export function showAuthModal(type smsLogin) {const modal $store(modal);setTimeout(() > {modal.$patch((state) > {state.auth type;});}, 100); }

docker操作的基本命令加容器的基本命令(仅供自己参考)

1、docker build:本地将一个docker文件打包成镜像 2、docker push:将自己打包的镜像传到镜像服务器上 3、docker pull:将镜像服务器上的镜像拉取到本地 4、docker images: 查看镜像服务器上的镜像 5、docker rmi:删…

编译成功!QT/6.7.2/Creator编译Windows64 MySQL驱动(MSVC版)

相邻你找了很多博文,都没有办法。现在终于找到了正宗。 参考 GitHub - thecodemonkey86/qt_mysql_driver: Typical symptom: QMYSQL driver not loaded. Solution: get pre-built Qt SQL driver plug-in required to establish a connection to MySQL / MariaDB u…

伊犁云计算22-1 raid 5 linux 配置

1  添加四块sata 硬盘  2  设置启动项为原来scsi 的硬盘 3  四块盘都是  fd   li&…

仓颉编程入门2,启动HTTP服务

上一篇配置了仓颉sdk编译和运行环境,读取一个配置文件,并把配置文件简单解析了一下。 前面读取配置文件,使用File.readFrom(),这个直接把文件全部读取出来,返回一个字节数组。然后又创建一个字节流,给文件…

Redis——持久化策略

Redis持久化 Redis的读写操作都是在内存上,所以Redis性能高。 但是当重启的时候,或者因为特殊情况导致Redis崩了,就可能导致数据的丢失。 所以Redis采取了持久化的机制,重启的时候利用之间持久化的文件实现数据的恢复。 Redis提…

python怎么打开编辑器

1、在电脑开始菜单中点击所有程序,找到Python程序,点击其中idle。 2、然后点击左上角的“File”,打开菜单,在下拉菜单中选择“New File”选项,就可打开python编辑器了。 3、在打开的python编辑器中就可以输入自己想写的…

Qwen大型语言模型系列的最新成果 ----Qwen2.5

通义千问2.5-7B-Instruct-GGUF 模型库 (modelscope.cn) apt install git-lfsgit lfs installgit clone https://www.modelscope.cn/qwen/Qwen2.5-7B-Instruct-GGUF.git

从源码到平台:食堂采购系统与供应链管理平台的开发详解

本篇文章,小编将从技术角度探讨如何基于源码开发一个食堂采购系统,并结合供应链管理平台的实现策略,帮助开发者与企业深入了解该系统的开发流程与关键要点。 一、食堂采购系统源码开发概述 食堂采购系统作为餐饮企业管理食材采购、库存以及…

综述论文“Towards Personalized Federated Learning”分享

综述论文“Towards Personalized Federated Learning”分享 文章目录 综述论文“Towards Personalized Federated Learning”分享I. 引言A. 联邦学习的分类B. 个性化联邦学习的动机C. 贡献 II. 个性化联邦学习的策略策略I:全局模型个性化策略II:学习个性…

无人机集群路径规划:麻雀搜索算法(Sparrow Search Algorithm, SSA)​求解无人机集群路径规划,提供MATLAB代码

一、单个无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径,使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一,它可以通过算法和模型来确定无人机的航迹,以避开障碍物、优化…

2024年中国研究生数学建模竞赛【华为杯】C题-数据驱动下磁性元件的磁芯损耗建模(代码+讲解+成品论文+答疑)

2024年中国研究生数学建模竞赛,即华为杯,研赛正式开赛了,本次比赛white学长团队选择了C题,各位小伙伴可以根据自己的擅长选择合适题目,比赛过程中请注意以下时间节点: 华为杯比赛时间节点 一、背景 随着国民…

大小端字节序 和 内存高低地址顺序

目录 1. 大小端字节序 1.1 什么是大小端字节序? 1.2 为什么有大小端字节序? 1.3 习题:用程序结果判断大端小端 2. 各种易混淆的高低地址顺序 2.1 监视窗口的地址表示【计算机标准展示方式】 2.2 横向地址表示 2.3 一个字节 与 多个字节 的地址…

在腾讯云申请https(我得是腾讯云服务器),通过宝塔设置https

参考 一键 HTTPS:https://cloud.tencent.com/document/product/400/58062 DNS 验证:https://cloud.tencent.com/document/product/400/54500?from_cn_redirect1 申请免费的证书 访问连接:https://console.cloud.tencent.com/ssl 点击页…

Python数据分析与可视化(Python绘图详解)

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

【更新】上市公司绿色专利申请及授权数据(2000-2023年)

一、数据介绍 数据名称:上市公司-绿色专利申请、授权数据 数据范围:A股上市公司 数据年份:2000-2023年 数据样本:61243条 数据来源:国家知识产权局 二、数据指标 年份股票代码股票简称行业名称行业代码省份城市区…

SQL - 基础语法

SQL作为一种操作命令集, 以其丰富的功能受到业内人士的广泛欢迎, 成为提升数据库操作效率的保障。SQL Server数据库的应用,能够有效提升数据请求与返回的速度,有效应对复杂任务的处理,是提升工作效率的关键。 由于SQL Servers数据库管理系统…

★pwn 更改pwn题libc保姆级教程★

★pwn 更改pwn题libc保姆级教程★ 🍚前言🥟安装🥟glibc-all-in-one下载与调整libc🥟patchelf更改libc🥟clibc的使用与分析 🍚前言 现在市面上有很多关于改libc的教程,但是基本有以下几个问题&a…

IDEA中实现springboot热部署

IDEA中实现springboot热部署 热部署: 每一次修改代码后会自动更新&#xff0c;无需每次重启 依赖(pom.xml) 修改后记得Reload一下 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><…

微信小程序IOS真机调试-onPullDownRefresh和onReachBottom不生效

切换真机调试2.0版本 勾选JS编译成ES5 如果使用了 uniapp&#xff0c;这里也需要勾选 重新启动