Transformer🔨
伟大无需多言。
《Attention Is All You Need》截止2025年,谷歌学术总引用次数排名第2。
关于大模型,我很早就了解了它的工作原理,但是和类与对象一样,很难在短时间内向他人讲述清楚。我认为抽象的内容最需要的是可视化的展示。
Transformer可视化这个项目很好的展示了Transformer的工作原理。
nanochat karpathy的这个项目设计为在单个 GPU 节点上运行,代码精简且易于修改。
以下是我配套的一些补充说明,目的是根据公式与原理逐步手写一个Transformer模型。
以下是我的基础模型配置:
dtype = 'bfloat16' # 数据类型:即每个数字占用16位(2字节),适当降低精度可以减少显存占用并轻度降智,精度低于int4时模型会断崖式降智。
batch_size = 64 # 批次大小:越大训练速度越快,但占用显存越多。对模型智力影响偏小。
block_size = 1024 # 上下文长度:越大,模型理解的上下文越多,智力越强,但占用显存越多。
n_embd = 768 # 词嵌入维度:越大,模型学习能力越强,但占用显存指数级增加。对模型智力有较大影响。
n_layer = 6 # Transformer层数:层数越多,模型表达能力越强,但训练和推理时间线性增加,显存占用也增加
n_head = 6 # 注意力头数:多头注意力机制的头数,通常设为词嵌入维度的约数,用于并行学习不同的注意力模式
# 以下和显存占用无关,和收敛速度有关
dropout = 0.0 # Dropout比率:初期设为 0 加速训练,后期如果过拟合调成 0.1,用于防止过拟合
learning_rate = 6e-4 # 学习率:控制模型参数更新的步长,过大可能导致训练不稳定,过小训练速度慢
max_iters = 5000 # 最大训练迭代次数:训练的总轮数,需要根据数据集大小和训练效果调整
lr_decay_iters = 5000 # 学习率衰减迭代次数:学习率开始衰减的迭代次数,通常与max_iters相同
min_lr = 6e-5 # 最小学习率:学习率衰减的下限,防止学习率过小导致训练停滞
# 基础配置
device = 'cuda' # 计算设备:'cuda'表示使用GPU加速,'cpu'表示使用CPU(速度较慢)
compile = True # 是否编译模型:True会使用PyTorch的torch.compile优化,可以提升训练和推理速度
通过这个单元的学习,你可以获得一个专属于自己的大模型,并可以用于自己的业务场景。同时可以回答下面的问题:
不同的大模型可以用同一套提示词吗?
使用了相同的语料、token分词算法。那么大模型的tokenizer是相似的。通常来说,同个公司的大模型,语料与算法是相似的。因此相似的提示词可以生效。
如果语料差距较大、token分词算法不同,那么提示词可能无法生效。
常见的分词算法例如:Byte-Pair Encoding (BPE)
原理:通过合并最频繁的字符对来构建词汇表,适合处理罕见词和新词。
大模型最大上下文长度可以动态变化吗?
-
Transformer模型的设计中,输入序列的长度通常在模型的构建时就被固定。这是因为模型的自注意力机制需要为每个输入token计算与其他所有token的关系,计算复杂度与输入长度的平方成正比。因此,固定的输入长度可以简化计算和内存管理。
-
Transformer使用位置编码(Positional Encoding)来 为输入序列中的每个token提供位置信息。位置编码的维度通常与模型的隐藏层维度相同,而位置编码的数量通常是根据预设的最大输入长度来定义的。如果输入长度超过了这个预设值,模型将无法正确处理超出部分的token。
-
在训练过程中,模型通常会使用固定长度的输入序列。如果训练数据中的序列长度超过了模型的最大输入长度,通常会进行截断或填充(padding),这可能导致信息丢失或计算效率低下。
Temperature 是如何影响模型输出的?
Temperature 是一个控制模型输出随机性的超参数。它会影响模型在选择下一个token时的概率分布。公式为:
- 当 Temperature 较高时,模型会倾向于选择概率较高的token,输出结果更确定。适合生成高质量、一致性强的文本。例如数学。
- 当 Temperature 较低时,模型会倾向于选择概率较低的token,输出结果更随机。适合生成多样性、创造性强的文本。
理论上,Temperature 的取值范围不限,但是实际使用中,通常取值在0-2之间,过高的 Temperature 会导致模型输出不准确。
如果你想生成一个童话的故事,且你可以设置了一个的 Temperature 为0,那么模型会倾向于生成传统的王子和公主的故事。因为语料中,王子和公主的故事是最多的。如果你想生成一个多样化的故事,你可以设置一个较高的 Temperature,例如1.5。但是这可能会导致故事的逻辑性不强,或者出现不符合逻辑的情节。
Embedding
提前准备:训练好的词表
- 文本 通过提前训练好的词表拆分为 Token(分词)
- Token 通过提前训练好的词表转化为 Token ID
- Token ID 通过提前训练好的词表转化为 Token Embedding(词嵌入)
- 将位置信息 转化为 位置编码(位置编码)
- 将 Token Embedding 和 位置编码 相加 得到 最终的输入向量
分词
训练大模型之前,一般先训练词嵌入向量库。训练前的词向量库会分配给每个token一个随机权重,如果不加训练也会输出乱码。训练后的词向量库每个token权重固定,相似词向量上体现一定的相似性。如果算力不够可以选择同时开源对应权重的【词嵌入向量库】
不同的模型用了不同的语料,因此维度大小、Token划分都是不一样的(为了避免维度爆炸,我们一般指定维度大小,通过频次聚类压缩,使得维度不会太大)。即不同模型编码器与解码器不一样,如果混用则可能输出乱码,即发现乱码时需要查看 词嵌入向量 模块与模型是否出自同一家厂商。
分词:首先需要使用某种分词器对输入文本进行分词处理,将其划分为一个个 token。这个过程中英文的单词有时需要下载一个分词表(通常为 tokenizer.json:分词器配置文件,用于文本的分词处理。),单词拆为本身和前缀与后缀,这个词表是人工标注的,这也是为什么英语是大模型首选语言。
词嵌入
构建词表:根据分词结果,统计所有 token 的出现频率,并依据设定的词表大小,选择保留的词汇。通常会保留高频率词汇,同时也会加入一些特殊符号(如垫零符 <PAD>、未知词 <UNK> 等)。通常为 tokenizer_config.json:分词器配置文件,定义了分词器的行为。分好后的词放在 vocab.json:词汇表文件,存储了模型所使用的词汇。
初始化嵌入矩阵:创建一个二维矩阵,行数等于词表大小,列数等于嵌入维度(即每个词向量的维度)。这个矩阵可以随机初始化,也可以通过预训练模型加载。
学习词嵌入:在训练神 经网络的过程中,嵌入矩阵会被当作可训练参数,随着反向传播不断调整,以使词向量能够更好地表示词语之间的语义关系。这一步骤可以通过多种方法实现。
获取词嵌入向量:当模型训练完成后,对于任何一个给定的 token,都可以通过查找嵌入矩阵快速得到其对应的词嵌入向量。
词嵌入的过程是分割好的词从【嵌入矩阵】中获取自己向量的过程,假设【嵌入矩阵】维度为4(GPT2选用768维)如下:
位置编码
这一步会将位置信息融入到词嵌入向量中。
词嵌入层和位置编码层是两个独立的模块,它们在模型中是并行工作的。
位置向量的维度与词嵌入向量维度相同。
词嵌入向量维度通常很高,这里示例的都有768维(通常取不低于300维,768可以看作3个256维拼接)。
我输入的词可能只有几个字,维度不够,如何转化为与词嵌入向量维度相同的向量?
现在主流的大模型使用的是RoPE (Rotary Positional Embedding, 旋转位置编码)。
假设你的词向量(Token Embedding)有 4 个维度(为了方便演示,用小数字),向量数值是:
RoPE 对 Q 和 K 向量进行旋转(不是独立的向量相加)。对每一对维度 (2i, 2i+1) 应用旋转矩阵(向量维度通常取2的整数倍或整数倍拼接,一定能两两配对上)。
旋转角度 (m 是位置)
例如,对于维度 2i 和 2i+1:
这直接修改 Q 和 K 向量,注入位置信息,而不增加额外参数。