深入理解大模型:GPT架构实现与层归一化原理解析!
文章详细介绍了大语言模型(LLM)的架构实现,特别是类GPT模型的框架搭建。内容涵盖LLM整体架构、GPT-2配置参数、DummyGPTModel占位架构的实现,以及层归一化的原理和代码实现。通过建立全局视角,帮助读者理解大模型的基本组件和工作流程,为后续深入实现完整GPT模型奠定基础。
简介
文章详细介绍了大语言模型(LLM)的架构实现,特别是类GPT模型的框架搭建。内容涵盖LLM整体架构、GPT-2配置参数、DummyGPTModel占位架构的实现,以及层归一化的原理和代码实现。通过建立全局视角,帮助读者理解大模型的基本组件和工作流程,为后续深入实现完整GPT模型奠定基础。
正文
在之前的文章中,我们由浅入深地学习了自注意力机制(从自注意力机制的原理到带有训练权重自注意力机制,再到因果自注意力机制,最后实现了一个多头自注意力机制)。自注意力机制之于大模型,就好比汽车的发动机,我们接下来要实现的 LLM 框架
,就相当于给汽车装上了轮子,方向盘等组件。
在之前的文章中,我们已经了解并实现了从:输入文本分词过程,到 tokenID 的生成,再到掩码多头注意力模块。为了就是将文本抓化成大模型能够读懂的“数字(向量)”。在输入大模型数据准备工作完成之后,我们就要开始着手搭建大模型了。
如图所示,我们从今天开始要做的就是上图中圈出来的第三部分。接下来我们首先从整体视角介绍模型架构,然后详细讲解各个组件。
再探 LLM 架构
在动手实现 LLM
之前,我们首先建立一个全局的 LLM
架构,因此,再次来看看 LLM
架构。
尽管模型规模庞大,但是,其结构却并没有想象中那么复杂,因为模型的许多组件是重复的。下图展示了一个类 GPT
的 LLM
的整体视图,并突出了其主要组成部分(Transformer block
)。LLM
是由多个这样的 Transformer
组成,其中每个 Transformer
将包含多个元素,我们在后续的文章中一个一个地讨论到。
大模型的配置文件
我们通过以下 Python
字典来定义小型 GPT-2
模型的配置,稍后将在代码示例中使用该配置:
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
}
在 GPT_CONFIG_124M
字典中,我们使用简明的变量名,以保证清晰且避免代码行过长:
vocab_size
指的是BPE
分词器使用的50,257
个词汇的词表大小。context_length
表示模型所能处理的最大输入token
数, 模型能处理的上下文的长度。emb_dim
表示嵌入维度,将每个token
转换为768
维的向量。n_layers
指定模型中Transformer
模块的层数,后续章节将对此详解。drop_rate
表示dropout
机制的强度(例如,0.1
表示丢弃10%
的隐藏单元),用于防止过拟合。qkv_bias
参数决定是否在多头注意力的查询、键和值的线性层中加入偏置向量。我们最初会禁用该选项,以遵循现代大语言模型的标准,之后在加载OpenAI
预训练的GPT-2
权重时再重新考虑该设置。
一直在更新,更多的大模型学习和面试资料已经上传带到CSDN的官方了,有需要的朋友可以扫描下方二维码免费领取【保证100%免费】👇👇
实现一个大模型的框架
使用上述配置,我们将从本文开始实现一个 GPT
占位架构(DummyGPTModel
),如下图所示。这我们将提供一个全局视图,了解所有组件是如何组合在一起的,以及在接下来的文章中需要编写哪些其他组件来组装完整的 GPT
模型架构。
图中显示的编号框说明了我们编写最终 GPT
架构所需理解的各个概念的顺序,总共分为六个步骤。
我们将从第 1 步开始,这是一个我们称之为 DummyGPTModel
的 GPT
占位架构,我们先实现整体的框架,然后在填充上述图中的 6 个部分,形成最终的 GPT
架构。
import torch
import torch.nn as nn
classDummyGPTModel(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(
*[DummyTransformerBlock(cfg) for _ in range(cfg["n_layers"])]) #A
self.final_norm = DummyLayerNorm(cfg["emb_dim"]) #B
self.out_head = nn.Linear(
cfg["emb_dim"], cfg["vocab_size"], bias=False
)
defforward(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
classDummyTransformerBlock(nn.Module):#C
def__init__(self, cfg):
super().__init__()
defforward(self, x):#D
return x
classDummyLayerNorm(nn.Module):#E
def__init__(self, normalized_shape, eps=1e-5):#F
super().__init__()
defforward(self, x):
return x
#A 为 TransformerBlock 设置占位符
#B 为 LayerNorm 设置占位符
#C 一个简单的占位类,后续将被真正的 TransformerBlock 替换
#D 该模块无实际操作,仅原样返回输入
#E 一个简单的占位类,后续将被真正的 DummyLayerNorm 替换
#F 此处的参数仅用于模拟LayerNorm接口
此代码中的 DummyGPTModel
类使用 PyTorch
内置的神经网络模块(nn.Module
)定义了一个简化版的类 GPT
模型。该类包括 token
嵌入、位置嵌入、dropout
、多个 Transformer
模块(DummyTransformerBlock
)、最终的层归一化(DummyLayerNorm
)以及线性输出层(out_head
)。模型配置通过 Python
字典传入,稍后将传入我们之前创建的 GPT_CONFIG_124M
字典。
forward
方法定义了数据在模型中的流动方式:计算输入索引的 token
嵌入和位置嵌入,应用 dropout
,通过 transformer block
处理数据,应用归一化,最后通过线性输出层生成 logits
。
上面的代码已经可以正常运行,不过需要先准备输入数据,在本节后面我们会看到运行效果。需要注意的是,目前代码中我们使用了 DummyLayerNorm
和 DummyTransformerBlock
作为 Transformer
模块和层归一化的占位符,实际的实现会在后续部分详细介绍。
再次整体理解大模型是如何工作的
为了能够理解其工作的原理,我们将准备输入数据并初始化一个新的 GPT
模型,以展示它的用法。
这张图片我们已经不止一次的在之前的文章中涉及到了。为了方便我们理解和演示,我们先从简单是输入开始,将简单的输入,运用的我们目前编写的大模型行框架中,看看会发生什么神奇的事情。
我们采用之前文章中提到的 tiktoken
分词器,然后输入两个文本,对这两个文本的批量输入进行分词。
import tiktoken
tokenizer = tiktoken.get_encoding("gpt2")
batch = []
txt1 = "Every effort moves you"
txt2 = "Every day holds a"
batch.append(torch.tensor(tokenizer.encode(txt1)))
batch.append(torch.tensor(tokenizer.encode(txt2)))
batch = torch.stack(batch, dim=0)
print(batch)
输出结果:这是 LLM
在训练期间接受到的输入。
LLM
接下来会对其进行预训练,任务就是生成或者预测下一个单词。
接下来,我们初始化一个拥有 1.24
亿参数的 DummyGPTModel
模型实例,并将分词后的数据批量输入到模型中:
torch.manual_seed(123)
model = DummyGPTModel(GPT_CONFIG_124M)
logits = model(batch)
print("Output shape:", logits.shape)
print(logits)
模型输出(通常称为 logits
)如下:
输出的张量有两行,每行对应一段文本。每段文本包含 4 个 token
,每个 token
是一个 50,257
维的向量,维度大小与分词器的词汇表相同。
嵌入层的维度为 50,257
,因为每个维度对应词汇表中的一个唯一 token
。在之后的处理中,我们会将这些 50,257
维向量转换回 token ID
,然后再解码成单词。
在对 GPT
架构及其输入输出进行了大概介绍之后,接下来的章节中将编写各个占位模块的实现,首先从用真实的层归一化类替换之前代码中的 DummyLayerNorm
开始。
使用归一化对激活值进行标准化
归一化的核心思想是将神经网络层的激活(输出)调整为均值为 0,方差为 1(即单位方差)。这种调整可以加速权重的收敛速度,确保训练过程的一致性和稳定性。正如上一节提到的,在 GPT-2
和现代 Transformer
架构中,层归一化通常应用于多头注意力模块的前后以及最终输出层之前。
在我们用代码实现层归一化之前,先通过下图了解一下层归一化的工作原理。
我们可以通过以下代码重现图中的示例,其中实现了一个具有 5 个输入和 6 个输出的神经网络层,并将其应用于两个输入样本:
torch.manual_seed(123)
# create 2 training examples with 5 dimensions (features) each
batch_example = torch.randn(2, 5)
layer = nn.Sequential(nn.Linear(5, 6), nn.ReLU())
out = layer(batch_example)
print(out)
打印出的张量中,第一行表示第一个输入样本的层输出,第二行表示第二个输入样本的层输出:
我们实现的神经网络层包含一个线性层,后接一个非线性激活函数 ReLU
,这是神经网络中的标准激活函数。
在对这些输出应用层归一化之前,我们先查看其均值和方差:
mean = out.mean(dim=-1, keepdim=True)
var = out.var(dim=-1, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
输出如下:
以上均值张量的第一行包含第一个输入样本的均值,第二行输出包含第二个输入样本的均值。
在计算均值或方差等操作时使用 keepdim=True
参数,可以确保输出张量的维度与输入张量相同,即使该操作通过 dim
参数减少了张量的维度。例如,如果不使用 keepdim=True
,返回的均值张量将是一个二维向量 [0.1324, 0.2170]
,而使用 keepdim=True
后,返回的张量则会是一个 2×1
的矩阵 [[0.1324], [0.2170]]
。
dim
参数用于指定张量中进行统计计算(如均值或方差)的维度,具体如下图所示。
对于二维张量(如矩阵),在进行均值或方差计算等操作时,使用 dim=-1
等同于使用 dim=1
,因为 -1 指的是张量的最后一个维度,即二维张量中的列。 在后续对 GPT
模型加入层归一化时,模型会生成形状为 [batch_size, num_tokens, embedding_size]
的三维张量,我们依然可以使用 dim=-1
对最后一个维度进行归一化,而无需将 dim=-1
改为 dim=2
。
接下来,我们将对之前获得的层输出应用层归一化。该操作包括减去均值,并除以方差的平方根(即标准差):
out_norm = (out - mean) / torch.sqrt(var)
print("Normalized layer outputs:\n", out_norm)
mean = out_norm.mean(dim=-1, keepdim=True)
var = out_norm.var(dim=-1, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
可以看到,归一化后的层输出现在也包含了负值,其均值为零,方差为 1:
请注意,输出张量中的值 2.9802e-08
是2.9802 × 10^-8
的科学记数法表示,用十进制形式表示为 0.0000000298
。这个值虽然非常接近 0,但由于计算机表示数字的精度有限,会产生微小的数值误差,因此不完全等于 0。
为提高可读性,我们可以将 sci_mode
设置为 False
,从而关闭张量值的科学计数法显示模式:
torch.set_printoptions(sci_mode=False)
print("Mean:\n", mean)
print("Variance:\n", var)
输出结果如下:
在本节内容中,我们已逐步实现并应用了层归一化。现在将这个过程封装到一个 PyTorch
模块中,以便后续在 GPT
模型中使用。
classLayerNorm(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))
defforward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
以上是对层归一化的具体实现,它作用于输入张量 x 的最后一个维度,该维度表示嵌入维度(emb_dim)。变量 eps 是一个小常数(epsilon),在归一化过程中加到方差上,以防止出现除零错误。scale和 shift 是两个可训练参数(与输入具有相同的维度)。大语言模型(LLM)在训练中会自动调整这些参数,以改善模型在训练任务上的性能。这使得模型能够学习适合数据处理的最佳缩放和偏移方式。
现在让我们在实践中尝试 LayerNorm
模块并将其应用于批量输入:
ln = LayerNorm(emb_dim=5)
out_ln = ln(batch_example)
mean = out_ln.mean(dim=-1, keepdim=True)
var = out_ln.var(dim=-1, unbiased=False, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
结果表明,层归一化代码运行正常,将两个输入的均值归一化为 0,方差归一化为 1:
在本文中,我们介绍了实现 GPT
架构所需的一个基础模块(LayerNorm
),如图所示。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
一直在更新,更多的大模型学习和面试资料已经上传带到CSDN的官方了,有需要的朋友可以扫描下方二维码免费领取【保证100%免费】👇👇
01.大模型风口已至:月薪30K+的AI岗正在批量诞生
2025年大模型应用呈现爆发式增长,根据工信部最新数据:
国内大模型相关岗位缺口达47万
初级工程师平均薪资28K(数据来源:BOSS直聘报告)
70%企业存在"能用模型不会调优"的痛点
真实案例:某二本机械专业学员,通过4个月系统学习,成功拿到某AI医疗公司大模型优化岗offer,薪资直接翻3倍!
02.如何学习大模型 AI ?
🔥AI取代的不是人类,而是不会用AI的人!麦肯锡最新报告显示:掌握AI工具的从业者生产效率提升47%,薪资溢价达34%!🚀
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
1️⃣ 提示词工程:把ChatGPT从玩具变成生产工具
2️⃣ RAG系统:让大模型精准输出行业知识
3️⃣ 智能体开发:用AutoGPT打造24小时数字员工
📦熬了三个大夜整理的《AI进化工具包》送你:
✔️ 大厂内部LLM落地手册(含58个真实案例)
✔️ 提示词设计模板库(覆盖12大应用场景)
✔️ 私藏学习路径图(0基础到项目实战仅需90天)
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
更多推荐
所有评论(0)