现在我们已经对分词和词嵌入有了基本认识,接下来可以更深入地探讨语言模型的工作原理。本章我们将解析Transformer语言模型的核心技术原理,重点聚焦文本生成模型,以帮助读者特别加深对生成式大语言模型(LLMs)运作机制的理解。
我们将同时探讨相关概念,并提供一些代码示例来演示这些原理。首先,我们将加载一个语言模型,并通过声明一个管道来为生成任务做好准备。在初次阅读时,可以选择跳过代码部分,集中精力理解相关概念。随后在第二次阅读时,可以通过代码来实际应用这些概念。
python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
# Load model and tokenizer
tokenizer = AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
model = AutoModelForCausalLM.from_pretrained(
"microsoft/Phi-3-mini-4k-instruct",
device_map="cuda",
torch_dtype="auto",
trust_remote_code=True,
)
# Create a pipeline
generator = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
return_full_text=False,
max_new_tokens=50,
do_sample=False,
)
Transformer模型 综述
让我们首先对Transformer模型进行一个高层次的概览,随后我们将探讨自2017年该模型问世以来,后续研究工作是如何对其进行改进的。
训练后的Transformer大语言模型的输入与输出
对Transformer语言模型行为的理解,最常见的方式是将其视为一个接收文本并生成回复的软件系统。当在足够大且高质量的数据集上训练出具备充分规模的文本输入-文本输出模型后,它就能够生成令人印象深刻且有实用价值的输出。图3-1展示了一个这样的模型在邮件撰写中的应用场景。

图 3-1 在高度抽象的层面上,Transformer LLMs 接收一个文本提示并输出生成的文本。
该模型并非一次性生成全部文本,而是逐个生成词元(token)。图3-2展示了针对输入提示生成四个词元的具体步骤。每个词元生成步骤都对应着模型的一次前向传播过程(用机器学习领域的术语来说,就是输入数据进入神经网络,通过计算图的运算流向输出端产生结果的过程)。

图 3-2. Transformer LLMs 一次生成一个词元,而不是一次性生成整个文本。
在每次生成词元后,我们会通过将输出词元追加到输入提示末尾的方式,作为下一个生成步骤的输入提示。如图3-3所示可以观察到这一过程。

图 3-3. 一个输出词元被添加到提示中,然后这段文本再次呈现给模型进行另一次前向传播以生成下一个词元
这让我们更准确地理解了模型的运作机制:它仅仅是基于输入提示来预测下一个词元。围绕神经网络的软件基本上通过循环运行模型,逐步扩展生成的文本直至完成。
在机器学习中,有一个专门术语用于描述这类通过消费自身早期预测结果来生成后续预测的模型(例如,用模型生成的第一个词元来生成第二个词元)。这类模型被称为自回归模型(autoregressive models)。这也是为什么在提及文本生成大语言模型(LLM)时,常称其为自回归模型------这种分类通常用于将文本生成模型与诸如BERT这样的文本表示模型(非自回归模型)区分开来。
当我们通过类似本文所示的方式使用大语言模型生成文本时,其底层运作的原理正是这种逐词元的自回归生成过程。
python
prompt = "Write an email apologizing to Sarah for the tragic gardening mishap.
Explain how it happened."
output = generator(prompt)
print(output[0]['generated_text'])
生成的文本如下:
Solution 1:
Subject: My Sincere Apologies for the Gardening Mishap
Dear Sarah,
I hope this message finds you well. I am writing to express my deep
我们可以看到模型开始撰写邮件,开头部分是主题。由于我们通过将max_new_tokens设置为50个令牌而建立的令牌限制,模型突然停止了。如果增加该限制,它将继续完成邮件。
前向传播的组成部分
除了循环结构外,还有两个关键的内部组件:分词器(tokenizer)和语言建模头(LM head)。图3-4展示了这些组件在系统中的位置。如前一章所述,分词器会将文本拆解为一系列token ID序列,而这些ID序列随后会作为模型的输入数据。
分词器之后是神经网络:由一系列Transformer块堆叠而成的结构负责完成所有处理任务。该堆叠结构之后连接着语言模型头(LM head),它将堆叠结构的输出转换为概率分数,以预测下一个最可能出现的词元(token)。

图3-4 一个Transformer LLM由一个分词器、一堆Transformer块和一个语言建模头组成
回想一下第2章,分词器包含一个词元表------分词器的词汇表。模型有一个与词汇表中每个词元相关联的向量表示(词元嵌入)。图3-5显示了一个词汇量为50,000个词元的模型的词汇表及相关词元嵌入。

图3-5 分词器有一个包含 50,000 个词元的词汇表。模型具有与这些嵌入相关联的词元嵌入。
计算的流程遵循箭头指示,自上而下进行。对于每个生成的token,流程会依次流经堆叠中的每个Transformer模块,随后传递至LM头(语言模型头),最终输出下一个token的概率分布(如图3-6所示)。
图 3-6. 在前向传播结束时,模型为词汇表中的每个词元预测一个概率分数。
语言模型头部(LM head)本身是一个简单的神经网络层。它是可附加到Transformer块堆栈上的多种可能的"头部"之一,用于构建不同类型的系统。其他类型的Transformer头部包括序列分类头部和词元分类头部。
我们可以通过打印模型变量来显示层的顺序。对于此模型,我们可以看到:
python
Phi3ForCausalLM(
(model): Phi3Model(
(embed_tokens): Embedding(32064, 3072, padding_idx=32000)
(embed_dropout): Dropout(p=0.0, inplace=False)
(layers): ModuleList(
(0-31): 32 x Phi3DecoderLayer(
(self_attn): Phi3Attention(
(o_proj): Linear(in_features=3072, out_features=3072, bias=False)
(qkv_proj): Linear(in_features=3072, out_features=9216, bias=False)
(rotary_emb): Phi3RotaryEmbedding()
)
(mlp): Phi3MLP(
(gate_up_proj): Linear(in_features=3072, out_features=16384, bias=False)
(down_proj): Linear(in_features=8192, out_features=3072, bias=False)
(activation_fn): SiLU()
)
(input_layernorm): Phi3RMSNorm()
(resid_attn_dropout): Dropout(p=0.0, inplace=False)
(resid_mlp_dropout): Dropout(p=0.0, inplace=False)
(post_attention_layernorm): Phi3RMSNorm()
)
)
(norm): Phi3RMSNorm()
)
(lm_head): Linear(in_features=3072, out_features=32064, bias=False)
)
观察这一结构,我们可以注意到以下重点:
• 这展示了模型的多层嵌套架构。模型主体称为"model",其下方连接着"lm_head"模块
• 在Phi3Model内部,我们能看到嵌入矩阵embed_tokens及其维度信息。该矩阵包含32,064个词元(token),每个词元对应3,072维的向量空间
• 暂时跳过dropout层,接下来主要组件是由32个Phi3DecoderLayer类型模块堆叠而成的Transformer解码器层
• 每个Transformer模块都包含注意力机制层和前馈神经网络(feedforward neural network,也称为mlp或多层感知机),这些内容我们将在本章后续详细解析
• 最终,lm_head接收3,072维向量作为输入,输出与模型词表大小相同的向量空间。该输出代表每个词元的概率分布,我们将基于此选择最终的输出词元
从概率分布中选择单个 词元 (采样/解码)
在处理结束时,模型的输出是词汇表中每个词元的概率分数,正如我们之前在图3-6中看到的。从概率分布中选择单个词元的方法称为解码策略。图3-7展示了这如何导致在一个示例中选择词元"Dear"。
最简单的解码策略总是选择概率分数最高的词元。实际上,这并不总是能为大多数用例带来最佳输出。更好的方法是添加一些随机性,有时选择第二或第三高概率的词元。这里的想法基本上是根据概率分数从概率分布中进行抽样,正如统计学家所说。
这意味着对于图3-7中的示例,如果词元"Dear"有40%的概率成为下一个词元,那么它就有40%的机会被选中(而不是贪婪搜索,它会因为它有最高的分数而直接选择它)。所以使用这种方法,所有其他词元都有机会根据它们的分数被选中。
图 3-7. 模型前向传播后概率最高的词元。我们的解码策略通过基于概率采样来决定输出哪些词元
每次选择得分最高的词元称为贪婪解码。如果你在LLM中将温度参数设置为零,就会发生这种情况。我们将在第6章中讨论这个概念。
让我们更仔细地看一下演示这个过程的代码。在这个代码块中,我们将输入词元通过模型传递,然后是lm_head:
python
prompt = "The capital of France is"
# Tokenize the input prompt
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
# Tokenize the input prompt
input_ids = input_ids.to("cuda")
# Get the output of the model before the lm_head
model_output = model.model(input_ids)
# Get the output of the lm_head
lm_head_output = model.lm_head(model_output[0])
现在,lm_head_output 的形状为 [1, 6, 32064]。我们可以使用 lm_head_output[0,-1] 访问最后一个生成词元的词元概率分数,其中索引 0 沿批次维度;索引 -1 让我们获得序列中的最后一个词元。这现在是一个包含所有 32,064 个词元的概率分数列表。我们可以获得得分最高的词元 ID,然后对其进行解码,得出生成的输出词元的文本:
token_id = lm_head_output[0,-1].argmax(-1)
tokenizer.decode(token_id)
在这种情况下,这变成了:
Paris
并行 词元 处理和上下文大小
Transformer最引人注目的特点之一是,与以前的语言处理神经网络架构相比,它们更适合并行计算。在文本生成中,我们可以通过观察每个词元是如何被处理的来初步了解这一点。从上一章我们知道,分词器会将文本分解成词元。然后,每个输入词元通过其自己的计算路径流动(至少这是一个很好的初步直觉)。我们可以在图3-8中看到这些单独的处理轨迹或流。

图 3-8 每个词元都通过其自己的计算流进行处理(正如我们稍后将看到的,在注意力步骤中它们之间存在一些交互)
当前Transformer模型一次能够处理的词元数量是有限的。这个限制被称为模型的上下文长度。一个具有4K上下文长度的模型只能处理4K个词元,并且只会有4K个这样的流。
每个词元流都以一个输入向量开始(嵌入向量和一些位置信息;我们将在本章后面讨论位置嵌入)。在流的末端,经过模型的处理,另一个向量作为结果出现,如图3-9所示。

图 3-9 每个处理流程都接受一个向量作为输入,并产生一个最终的结果向量,其大小相同(通常称为模型维度)。
在文本生成中,只使用最后一个流的输出结果来预测下一个词元。该输出向量是唯一输入到语言模型(LM)头部的信息,因为它计算下一个词元的概率。
您可能会想知道,如果我们丢弃除了最后一个词元之外的所有词元的输出,为什么还要费心计算所有的词元流。答案是,之前流的计算是必需的,并且在计算最终流时被使用。是的,我们没有使用它们的最终输出向量,但我们在Transformer块的注意力机制中使用了早期输出(在每个Transformer块中)。
如果你在跟着代码示例操作,回想一下 lm_head 的输出形状是 [1, 6, 32064]。这是因为输入的形状是 [1, 6, 3072],即一个包含六个词元的输入字符串批次,每个词元由一个大小为 3,072 的向量表示,这些向量对应于 Transformer 块堆叠后的输出向量。我们可以通过打印来访问这些矩阵并查看它们的维度:
model_output[0].shape
这会输出:
torch.Size([1, 6, 3072])
同样,我们可以打印LM头的输出:
lm_head_output.shape
这会输出:
torch.Size([1, 6, 32064])
通过缓存键和值加速生成
回想一下,在生成第二个词元时,我们只需将输出词元追加到输入中,然后再次通过模型进行前向传播。如果我们让模型能够缓存之前计算的结果(特别是注意力机制中的一些特定向量),那么我们就不再需要重复之前流的计算。这次唯一需要计算的是最后一个流。这是一种称为键和值(kv)缓存的优化技术,它可以显著加快生成过程的速度。键和值是注意力机制的一些核心组件,我们将在本章后面看到。
图3-10展示了在生成第二个词元时,由于缓存了之前流的结果,因此只有一个处理流处于活动状态。
图3-10 在生成文本时,缓存先前词元的计算结果而不是重复相同的计算非常重要
在Hugging Face Transformers中,默认启用缓存。我们可以通过将use_cache设置为False来禁用它。我们可以通过请求长文本生成并通过计时有缓存和无缓存的生成来看到速度的差异:
python
prompt = "Write a very long email apologizing to Sarah for the tragic gardening
mishap. Explain how it happened."
# Tokenize the input prompt
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
input_ids = input_ids.to("cuda")
然后我们计时使用缓存生成100个词元所需的时间。我们可以使用Jupyter或Colab中的%%timeit魔术命令来计时执行所需的时间(它会运行命令多次并获取平均值):
python
%%timeit -n 1
# Generate the text
generation_output = model.generate(
input_ids=input_ids,
max_new_tokens=100,
use_cache=True
)
在使用T4 GPU的Colab上,这将是4.5秒。但是,如果我们禁用高速缓存,这需 要多长时间呢?
python
%%timeit -n 1
# Generate the text
generation_output = model.generate(
input_ids=input_ids,
max_new_tokens=100,
use_cache=False
)
这相当于21.8秒。这是一个巨大的差异。实际上,从用户体验的角度来看,即使是四秒的生成时间对于盯着屏幕等待模型输出的用户来说也往往是很长的时间。这也是为什么大型语言模型(LLM)API在模型生成输出词元时就开始流式传输它们,而不是等待整个生成过程完成的原因之一。
深入Transformer 内部
现在,我们可以重点探讨绝大多数计算处理的核心发生地------Transformer模块。如图3-11所示,Transformer架构的大型语言模型(LLM)由一连串的Transformer模块构成(数量上从原始Transformer论文中提出的6个基础模块,到当代大型语言模型中经常使用的上百个模块不等)。每一个模块对输入数据进行处理后,会将处理结果传递给后续的模块链式处理。这种层级联动的架构,通过模块间的信息逐级传递实现深层语义的理解。
图 3-11. Transformer LLM 的大部分处理过程发生在一系列 Transformer 模块内部,每个模块将其处理结果作为输入传递给后续模块。
一个Transformer模块(图3-12)由两个连续的组件组成:
- 注意力层的主要功能是整合来源于其他输入标记及位置的相关信息。
- 前馈层承载了模型的主体处理能力。
图 3-12 一个 Transformer 模块由一个自注意力层和一个前馈神经网络组成。
前馈神经网络 概览
一个简单的例子可以帮助我们直观理解前馈神经网络的工作原理:假设我们将输入"肖申克"传递给语言模型(参照1994年那部著名电影),该模型就会生成"救赎"作为概率最高的下一个词语输出。
前馈神经网络(涵盖所有模型层)是这些信息的来源,如图3-13所示。当模型经过成功训练并能准确建模一个大规模文本档案库(其中包含大量关于《肖申克的救赎》的提及)时,它便学习并存储了使其在此任务中取得成功所需的信息(及行为模式)。

图3-13. Transformer模块中的前馈神经网络组件可能完成了模型的大部分记忆和插值工作。
要让一个大语言模型(LLM)训练成功,它需要记住大量信息。但它不只是一个庞大的数据库。记忆只是其生成惊艳文本的"配方"中的一个成分。模型能够利用相同的机制,在数据点和更复杂的模式之间进行"推演",从而实现泛化------这意味着即使面对过去从未见过、也不在其训练集中的输入时,它仍能表现出色。
当你使用现代商业大型语言模型(LLM)时,你得到的输出并不是前面严格意义上的"语言模型"所提到的那些。将"肖申克"传递给像GPT-4这样的聊天LLM会产生如下输出:
"《肖申克的救赎》是1994年由弗兰克·达拉邦特执导的电影,改编自斯蒂芬·金所著的中篇小说《丽塔·海华丝与肖申克的救赎》......等等。"
这是因为原始语言模型(如GPT-3)对人们来说很难恰当使用。这就是为什么语言模型随后会在指令调优以及人类偏好和反馈调优上进行训练,以符合人们对模型应该输出的内容的期望。
注意力层 概览
要正确地建模语言,语境至关重要。仅依赖前序词元的简单记忆与插值所能达到的效果十分有限。这一点我们很清楚,因为这类方法在神经网络出现前曾是构建语言模型的主要方式(参见丹尼尔·尤拉夫斯基与詹姆斯·H·马丁合著的《语音与语言处理》第三章"N元语言模型")。
注意力机制是一种能够帮助模型在处理特定词元时整合上下文的机制。请思考以下提示:
"狗追逐松鼠,因为它"(The dog chased the squirrel because it)
当模型需要预测"it"之后的内容时,必须确定"it"所指代的实体:是"狗"(dog)还是"松鼠"(squirrel)?(译者著 "此时注意力机制会分析每个词的关联度------通过将"它"与句子中的每个名词(如"狗"和"松鼠")计算注意力得分,模型会发现"追逐"行为的主动方(狗)与被动方(松鼠)中,松鼠作为末位被提到且是被动对象,因此更大概率判断"it"指代的是松鼠。这种动态上下文权重的智能分配,正是注意力机制区别传统序列处理的核心特征。 ")
在训练好的Transformer大型语言模型中,注意力机制负责完成这一判定。它会将上下文中的信息融入"it"这一标记的表示(representation)中。如图3-14所示,我们可以看到一个简化版本的实现方式。
图 3-14 自注意力层结合了之前位置的相关信息,这些信息有助于处理当前词元
该模型基于其在训练数据集中观察和学习到的模式进行上述操作。前面的句子可能提供了更多线索,例如将这只狗称为"她",从而明确"它"指代的是松鼠。
注意力是你所需的一切
值得对注意力机制进行更深入的探讨。图3-15展示了该机制最精简的版本示意图。图中显示了进入注意力层的多个词元位置------最后一个箭头(粉色表示)对应正在被处理的当前位置。注意力机制会处理该位置对应的输入向量,将上下文中的相关信息整合到这个向量中,最终产生该位置的输出向量。

图3-15. 注意力机制的简化框架:包含输入序列和正在处理的当前位置。由于我们主要关注该位置,示意图展示了一个输入向量和一个输出向量,该输出向量根据注意力机制整合了序列中先前元素的信息
注意力机制主要涉及两个关键步骤:
1.通过评分机制评估先前各个输入标记与当前处理标记的相关性(如粉色箭头所示)。
2.基于这些评分权重,将不同位置的信息整合为单一输出向量。
图3-16清晰呈现了这两个核心阶段的操作过程。
图 3-16 注意力由两个主要步骤组成:对每个位置进行相关性评分,然后我们根据这些分数合并信息。
为了使Transformer具备更广泛的注意力能力,其注意力机制被复制并多次并行执行。每一次并行的注意力应用都构成一个注意力头。这种做法增强了模型对输入序列中复杂模式的建模能力,这些模式往往需要同时关注不同类型的特征。
图3-17直观地说明了注意力头如何实现并行化运作:既展示了前置的信息分割步骤,也呈现了后续将各注意力头结果融合的过程。
图3-17 我们通过在并行中多次进行注意力操作,提高了模型的容量以关注不同类型的信息,从而获得了更好的LLM。
如何计算注意力
我们先来看单个注意力头内部的注意力是如何计算的。在开始计算之前,让我们先明确以下内容作为起始点:
• 注意力层(生成式LLM的)正在对单个位置的注意力进行计算处理。
• 该层的输入包括:
当前位置或词元的向量表示
之前词元的向量表示
• 目标是生成当前位置的新表示,其中包含来自之前词元的相关信息:
举个例子,假如我们正在处理句子"Sarah fed the cat because it,"的最后一个位置时,我们希望"it"能指代前面的"the cat"------这时注意力机制就会把来自"cat"词元(token)的"猫咪信息"编码进去。
• 训练过程产生三个投影矩阵,这些矩阵生成在此计算中相互作用的组件:
查询(query)投影矩阵
键(key)投影矩阵
值(value)投影矩阵
图3-18展示了所有这些组件在注意力计算开始之前的初始状态。为了简化说明,我们仅观察其中一个注意力头,因为其它注意力头的计算过程完全相同,只是使用各自的投影矩阵进行运算。

图 3-18 在开始自注意力计算之前,我们有层的输入以及查询、键和值的投影矩阵
注意力机制的工作原理是先将输入数据与投影矩阵相乘,生成三个新的矩阵------查询(queries)矩阵、键(keys)矩阵和值(values)矩阵。这些矩阵将输入词元的信息分别投射到三个不同的向量空间,进而实现注意力计算的两个关键阶段:
1.相关性评分
2.信息整合
图3-19展示了这三个新矩阵,并说明了所有三个矩阵的底行如何与当前位置相关联,而位于其上方的行则与之前的位置相关联。

图 3-19:注意力机制通过查询矩阵(queries)、键矩阵(keys)和值矩阵(values)的交互作用实现。这些矩阵是通过将该层的输入与投影矩阵(projection matrices)相乘而生成的。
自注意力:相关性评分
在生成式 Transformer 中,我们每次仅生成一个词元。这意味着每次处理单个位置时,注意力机制的目标聚焦于当前处理位置:通过提取其他相关位置的信息(比如前文内容),为当前处理位置提供预测依据。
注意力机制中相关性评分的计算步骤是:将当前位置的查询向量与键矩阵相乘。这个过程会生成一系列评分分数,用于表征每个历史标记的相关程度。通过softmax函数对这些分数进行归一化处理后,所有评分的总和将等于1。图3-20展示了这种计算方式最终得到的相关性权重分布。
图3-20.通过与当前位置关联的查询(query)和键矩阵(keys matrix)相乘,可以实现对先前标记相关性程度的评分。
自注意力:信息 整合
在获得各标记之间的相关性分数后,我们将与每个标记相关联的值向量(value vector)乘以该标记的得分。通过将这些经过加权的向量求和,即可得到当前注意力步骤的输出,如图3-21所示。
图 3-21 注意力机制通过将相关位置的相关性分数与其对应的值向量相乘,从而整合之前位置的相关信息。
对Transformer架构的 最新 改进
自从Transformer架构发布以来,已经做了大量工作来改进它并创建更好的模型。这包括在更大的数据集上进行训练,优化训练过程和学习率,但也涉及到架构本身。在撰写本文时,原始Transformer的许多想法仍然保持不变。有一些架构方面的想法被证明是有价值的。它们有助于提高像Llama 2这样的较新Transformer模型的性能。在本章的最后一部分,我们将回顾Transformer架构的一些重要近期发展。
更高效的注意力
研究界最关注的领域是Transformer的注意力层。这是因为注意力计算是整个过程中计算量最大的部分。
局部/稀疏注意力
随着Transformer模型逐渐变大,诸如稀疏注意力("Generating long sequences with sparse transformers")和滑动窗口注意力("Longformer: The long-document transformer")之类的想法提高了注意力计算的效率。稀疏注意力限制了模型可以关注的前一个词元的上下文,如图3-22所示。
图 3-22. 局部注意力通过仅关注少量先前位置来提升性能
一个包含这种机制的模型是GPT-3。但它并不对所有Transformer块都使用这种机制------如果模型只能看到少量先前的词元,生成的质量将大幅下降。GPT-3架构将全注意力Transformer块和高效注意力Transformer块交织在一起。因此,Transformer块在全注意力(例如,第1和第3块)和稀疏注意力(例如,第2和第4块)之间交替。
为了展示不同类型的注意力,请查看图3-23,该图显示了不同注意力机制的工作原理。每个图形都展示了在处理当前词元(深蓝色)时可以关注哪些先前的词元(浅蓝色)。
图3-23. 全注意力与稀疏注意力。图3-24解释了着色。(来源:"Generating long sequences with sparse transformers")
每一行对应一个正在处理的词元。颜色编码表示模型在处理深蓝色单元格中的词元时能够关注哪些词元。图3-24更清晰地描述了这一点。

图3-24 注意力图显示了当前正在处理的词元,以及注意力机制允许其关注的先前词元。
该图还展示了译码器Transformer模块(构成大多数文本生成模型的部分)的自回归特性;它们只能关注之前的词元。与此形成对比的是BERT,它可以关注两侧(因此BERT中的B代表双向)。
多查询和分组查询注意力
一种较新的Transformer高效注意力调整方法是分组查询注意力(("GQA:Training generalized multi-query transformer models from multi-head checkpoints"),Llama 2和3等模型使用了这种方法。图3-25展示了这些不同类型的注意力,下一节将继续解释它们。
图3-25. 不同类型注意力的比较:原始多头注意力、分组查询注意力和多查询注意力(来源:"Fast transformer decod‐ing: One write-head is all you need")
分组查询的关注建立在多查询关注的基础上 ("Fast transformer decod‐ing: One write-head is all you need")。这些方法通过减少所涉及的矩阵的大小来提高较大模型的推理可伸缩性。
优化注意力:从多头到多查询到分组查询
在本章前面,我们展示了Transformer论文是如何描述多头注意力的。图解Transformer详细讨论了如何使用查询、键和值矩阵来进行注意力操作。图3-26展示了每个"注意力头"如何为给定输入计算其自己独特的查询、键和值矩阵。
多头查询注意力优化这一点的方式是在所有头之间共享键和值矩阵。因此,每个头唯一的矩阵将是查询矩阵,如图3-27所示。
图 3-26. 注意力是通过查询、键和值的矩阵来进行的。在多头注意力中,每个头都有这些矩阵的不同版本。

图 3-27. 多查询注意力通过在所有注意力头之间共享键和值矩阵,呈现了一种更高效的注意力机制。
随着模型规模的增大,这种优化可能会变得过于苛刻,我们可以多使用一些内存来提高模型的质量。这就是分组查询注意力机制的用武之地。它不是将键和值矩阵的数量减少到一个,而是允许我们使用更多(但少于头数的数量)。图3-28展示了这些组以及每组注意力头如何共享键和值矩阵。
图 3-28. 分组查询注意力牺牲了一点多查询注意力的效率,以换取通过允许多组共享键/值矩阵而获得的质量大幅提高;每组都有其各自的一组注意力头。
快速注意力
闪存注意力(Flash Attention)是一种流行的方法和实现,为在GPU上训练和推理Transformer LLMs提供了显著的速度提升。它通过优化加载和在GPU的共享内存(SRAM)和高带宽内存(HBM)之间移动的值来加速注意力计算。这在论文"FlashAttention: Fast and memory-efficient exact attention with IO-awareness"
和后续的"FlashAttention-2: Faster attention with bet‐ter parallelism and work partitioning"中有详细描述。
Transformer 模块
请记住,Transformer 模块的两个主要组成部分是一个注意力层和一个前馈神经网络。更详细地观察该模块还会显示我们可以在图 3-29 中看到的残差连接和层归一化操作。
图3-29. 来自原始Transformer论文的一个Transformer模块。
在撰写本文时,最新的Transformer模型仍然保留了主要组件,但我们可以从图3-30中看到进行了一些调整。在这个版本的Transformer块中,我们看到的一个区别是,在注意力和前馈层之前进行归一化。据报道,这可以减少所需的训练时间(参考文献:"Transformer架构中的层归一化")。此处归一化的另一个改进是使用RMSNorm,它比原始Transformer中使用的LayerNorm更简单、更高效(参考文献:"均方根层归一化")。最后,与原始Transformer的ReLU激活函数相比,像SwiGLU这样的新变体(在"GLU变体改进Transformer"中描述)现在更为常见。
图3-30 2024年时代的Transformer模型(如Llama 3)的Transformer模块具有一些调整,如预归一化和使用分组查询注意力机制及旋转嵌入优化的注意力机制
位置嵌入(RoPE)
位置嵌入自原始Transformer以来一直是关键组件。它们使模型能够跟踪序列/句子中词元/单词的顺序,这是语言中不可或缺的信息来源。在过去的几年里提出了许多位置编码方案,旋转位置嵌入(或"RoPE",在"RoFormer:增强型Transformer与旋转位置嵌入"中引入)尤其重要。
原始Transformer论文和一些早期变体使用了绝对位置嵌入,本质上将第一个词元词元为位置1,第二个为位置2...等等。这些方法可以是静态的(位置向量使用几何函数生成)或学习的(模型训练在学习过程中为它们分配值)。当我们扩大模型时,这些方法会出现一些挑战,这要求我们找到提高它们效率的方法。
例如,在高效训练具有大上下文的模型时,一个挑战是训练集中的许多文档比该上下文短得多。将整个4K上下文分配给一个短的10个单词的句子将是低效的。因此,在模型训练期间,文档被打包到训练批次中的每个上下文中,如图3-31所示。
图 3-31. 打包是将简短的训练文档高效地组织到上下文中的过程。它包括在单个上下文中对多个文档进行分组,同时最小化上下文末尾的填充。
通过阅读"高效序列打包以避免交叉污染:在不影响性能的情况下加速大型语言模型"以及观看"介绍打包BERT以实现自然语言处理中2倍训练速度提升"的精彩视觉效果,了解更多关于打包的信息。
位置嵌入方法必须适应这一和其他实际考虑。例如,如果文档50从位置50开始,那么如果我们告诉模型第一个词元是数字50,就会误导模型,这将影响其性能(因为它会假设有先前的上下文,而实际上较早的词元属于模型应忽略的不同且无关的文档)。
旋转嵌入是一种编码位置信息的方法,它可以捕捉绝对和相对词元位置信息,而不是在前向传播开始时添加的静态绝对嵌入。它基于在嵌入空间中旋转向量的思想。在前向传播过程中,它们在注意力步骤中被添加,如图3-32所示。
图3-32 旋转嵌入应用于注意力步骤中,而不是在前向传播的开始。
在注意力过程中,位置信息在我们进行相关性评分的乘法运算之前,被特别地混合到查询和键矩阵中,如图3-33所示。
图3-33. 在自注意力中的相关性评分步骤之前,将旋转位置嵌入添加到词元的表示中。
其它架构 实验和改进
许多针对Transformer的调整不断被提出和研究。"A Survey of Transformers"突出了一些主要方向。Transformer架构也在不断适应LLM之外的领域。计算机视觉是一个有很多Transformer架构研究发生的领域(参见:"Trans‐ formers in vision: A survey" and "A survey on vision transformer" )。其他领域包括机器人技术(参见"Open X-Embodiment: Robotic learning datasets and RT-X models")和时间序列(参见 "Transformers in time series: A survey")。
总结
在本章中,我们讨论了Transformer的主要直觉和使最新的Transformer LLM成为可能的最新发展。我们讨论了许多新概念,让我们分解一下本章讨论的关键概念:
• Transformer LLM一次生成一个词元。
• 该输出词元被附加到提示中,然后这个更新的提示再次呈现给模型进行另一次前向传播以生成下一个词元。
• Transformer LLM的三个主要组成部分是分词器、Transformer块的堆栈和语言建模头。
• 分词器包含模型的词元词汇表。模型具有与这些词元相关联的词元嵌入。将文本分解为词元,然后使用这些词元的嵌入是词元生成过程中的第一步。
• 前向传播一次流经所有阶段,一个接一个。
• 在过程接近尾声时,LM头对下一个可能词元的概率进行评分。解码策略告知哪个实际词元作为此生成步骤的输出(有时它是最可能的下一个词元,但并非总是如此)。
• Transformer之所以表现出色,一个原因是它能够并行处理词元。每个输入词元流入它们各自的处理轨道或流。流的数量是模型的"上下文大小",这代表模型可以操作的最大词元数。
• 由于Transformer LLM循环一次生成一个词元的文本,因此缓存每个步骤的处理结果是一个好主意,这样我们就不会重复处理工作(这些结果存储在层内的各种矩阵中)。
• 大部分处理发生在Transformer块内。这些由两个组件组成。其中一个是前馈神经网络,它能够存储信息并根据其训练过的数据进行预测和插值。
• Transformer块的第二个主要组成部分是注意力层。注意力结合上下文信息,使模型更好地捕捉语言的细微差别。
• 注意力发生两个主要步骤:(1)评分相关性;(2)组合信息。
• Transformer注意力层并行执行多个注意力操作,每个操作都在一个注意力头内进行,它们的输出被聚合起来形成注意力层的输出。
• 通过在所有头之间或头组之间(分组查询注意力)共享键和值矩阵,可以加速注意力计算。
• 像Flash Attention这样的方法通过优化在GPU的不同内存系统上的操作方式来加快注意力计算速度。
Transformer继续在语言模型和其他领域及应用中看到新的发展和提出的调整,以在不同场景中改进它们。
在本书的第二部分,我们将介绍LLM的一些实际应用。
在第4章,我们从文本分类开始,这是语言AI中的一个常见任务。下一章将介绍应用生成模型和表示模型。