Transformer 架构无疑是现代深度学习领域最具变革性的创新之一。自 2017 年在著名论文 《Attention Is All You Need》 中提出以来,Transformer 已成为大多数语言相关建模的首选方法,包括所有大型语言模型 (LLM),例如 GPT 系列。
随着这些模型的复杂性和规模持续增长,优化推理速度的需求愈发迫切,尤其是在聊天应用等需要实时响应的场景中。键值(KV)缓存正是提升推理效率的一种巧妙优化策略。接下来,让我们深入探讨其工作原理以及适用场景。
Transformer 架构概述
在深入研究 KV 缓存之前,我们需要先了解一下 transformer 中使用的注意力机制。
了解它的工作原理是发现和理解 KV 缓存如何优化 transformer 推理的必要条件。
自注意力机制
自注意力机制允许模型在生成下一个标记时 “注意” 输入序列的特定部分。
例如,在生成句子 “She poured the coffee into the cup(她将咖啡倒入杯子)”时,模型可能会更加注意单词 “poured” 和 “coffee”,以预测下一个单词是“into”,因为这些单词为接下来可能出现的内容提供了上下文。
从数学上讲,自注意力的目标是将每个输入(嵌入的标记)转换为所谓的上下文向量,该向量结合了给定文本中所有输入的信息。
考虑文本“She poured coffee”。注意力将计算三个上下文向量,每个输入标记一个(我们假设标记是单词)。
为了计算上下文向量,自注意力机制计算三种中间向量:查询、键和值。
下图逐步展示了如何计算第二个单词 “poured” 的上下文向量。
我们将三个标记化输入分别表示为x1、x2 和 x3。
图中将它们描绘为具有三个元素的向量,但实际上,它们的长度将有数百或数千个元素。
作为第一步,自注意力将每个输入分别与两个权重矩阵 和 相乘。现在正在计算上下文向量的输入(在我们的例子中是x2 )还与第三个权重矩阵 相乘。所有三个 W 矩阵都是你通常的神经网络权重,在学习过程中随机初始化和优化。此步骤的输出是每个输入的键(k)和值(v)向量,以及正在处理的输入的额外查询(q)向量。
在第二步中,将每个输入的键向量乘以正在处理的输入的查询向量(我们的q2)。然后对输出进行归一化以产生注意权重。在我们的示例中, 是输入“She” 和 “poured” 之间的注意权重。
最后,每个注意力权重都乘以其对应的值向量。然后将输出相加以产生上下文向量 z。在我们的示例中,上下文向量 z2 对应于输入x2,即“poured”。上下文向量是自注意力模块的输出。
import torch
class SelfAttention_v2(torch.nn.Module):
def __init__(self, d_in, d_out, qkv_bias=False):
super().__init__()
self.W_query = torch.nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = torch.nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = torch.nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.T
attn_weights = torch.softmax(
attn_scores / keys.shape[-1]**0.5, dim=-1
)
context_vec = attn_weights @ values
return context_vec
高级自注意力模块
上面描述的自注意力机制是最简单的原始形式。
当今最大的 LLM 通常使用略微修改的变体,这些变体通常在三个方面与我们的基本风格不同。
-
注意力是有因果关系的。
-
Dropout 用于注意力权重。
-
使用多头注意力机制。
因果注意力意味着模型在预测下一个单词时应该只考虑序列中的前一个标记,防止它“向前看”未来的单词。回到我们的例子 “She poured coffee”,当模型被赋予单词 “She” 并现在试图预测下一个单词(“poured”)时,它不应该计算或访问 “coffee” 和任何其他单词之间的注意力权重,因为 “coffee” 这个词还没有出现在文本中。因果注意力通常是通过用零掩盖注意力权重矩阵的“向前看”部分来实现的。
接下来,为了减少训练过程中的过度拟合,通常会对注意力权重应用 dropout。这意味着在每次前向传递中,其中一些权重会被随机设置为零。
最后,基本注意力可以称为单头注意力,这意味着只有一组 、 和 矩阵。增加模型容量的一个简单方法是切换到多头注意力。这归结为拥有多组 W 矩阵,从而拥有多个查询、键和值矩阵,以及每个输入的多个上下文向量。
此外,一些 Transformer 对注意力模块进行了额外的修改,以提高速度或准确性。
三种流行的 Transformer 是:
-
分组查询注意力
不再逐个查看每个输入标记,而是对标记进行分组,让模型可以同时关注相关的单词组,从而加快处理速度。
-
分页注意力
注意力被分解为“页面”或标记块,因此模型一次处理一页,从而使得非常长的序列的处理速度更快。
-
滑动窗口注意力
该模型仅关注每个标记周围固定“窗口”内的附近标记,因此它专注于局部上下文,而无需查看整个序列。
所有这些实现自注意力的先进方法都不会改变其基本前提和它所依赖的基本机制:总是需要将键乘以查询,然后再乘以值。事实证明,在推理时,这些乘法显示出严重的低效率。让我们看看为什么会这样。
什么是键值缓存
当我们通过传递 “She” 来提示模型开始生成时,它将生成一个单词,例如“poured”(为了避免干扰,我们继续假设一个 token 是一个单词)。然后,我们可以将 “She poured” 传递给模型,它会生成 “coffee”。接下来,我们传递 “She poured coffee” 并从模型中获取序列结束 token,表明它认为生成已完成。
这意味着我们已经运行了三次前向传递,每次将查询乘以键以获得注意力分数(稍后与值的乘法也是如此)。
在第一次正向传递中,只有一个输入标记(“She”),因此只有一个键向量和一个查询向量。我们将它们相乘以获得 q1k1 注意力得分。
接下来,我们将 “She poured” 传递给模型。
现在它看到两个输入标记,因此我们的注意力模块内部的计算如下所示。
我们进行了乘法运算来计算三个项,但 q1k1 是不必要的计算,我们之前已经计算过了!
这个 q1k1 元素与上一次前向传递中的相同,因为:
-
q1 的计算方式是将输入(“she”)的嵌入乘以 Wq 矩阵
-
k1 的计算方式是将输入(“she”)的嵌入乘以 Wk 矩阵
-
嵌入和权重矩阵在推理时都是恒定的
注意注意力得分矩阵中灰色的条目被零掩盖以实现因果注意力。
最后,这是我们第三次前向传递中注意力模块内部的计算。
我们花费了计算力气来计算六个值,其中一半我们已经知道,不需要重新计算!
你可能已经对键值缓存的含义有所了解。在推理时,当我们计算键 ( K ) 和值 ( V ) 矩阵时,我们会将其元素存储在缓存中。缓存是一种辅助存储器,可以从中进行高速检索。在生成后续标记时,我们仅计算新标记的键和值。
例如,第三次前向传递在缓存的情况下看起来如下:
在处理第三个 token 时,我们不需要重新计算前一个 token 的注意力分数。我们可以从缓存中检索前两个 token 的键和值,从而节省计算时间。
评估键值缓存的影响
键值缓存可能会对推理时间产生重大影响。影响的大小取决于模型架构。可缓存的计算越多,减少推理时间的潜力就越大。
我们将首先定义一个计时器上下文管理器来计算生成时间。
import time
class Timer:
def __enter__(self):
self._start = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
self._end = time.time()
self.duration = self._end - self._start
def get_duration(self) -> float:
return self.duration
接下来,我们从 Hugging Face Hub 加载模型,设置标记器并定义提示。
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
model_name = "EleutherAI/gpt-neo-1.3B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
input_text = "Why is a pour-over the only acceptable way to drink coffee?"
最后,我们可以定义运行模型推理的函数。
def generate(use_cache):
input_ids = tokenizer.encode(
input_text,
return_tensors="pt").to(device)
output_ids = model.generate(
input_ids,
max_new_tokens=100,
use_cache=use_cache,
)
注意 use_cache 用于控制是否使用 KV 缓存。
通过此设置,我们可以测量有无 KV 缓存的平均生成时间。
import numpy as np
for use_cache in (False, True):
gen_times = []
for _ in range(1):
with Timer() as t:
generate(use_cache=use_cache)
print(t.get_duration())
gen_times += [t.get_duration()]
print(
f"Average inference time with use_cache={use_cache}: ",
f"{np.round(np.mean(gen_times), 2)} seconds",
)
可以看到,缓存带来的加速几乎增加了三倍。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(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 的正确特征了。