Attention 注意力机制
围绕 注意力机制 :
- 什么是、为什么需要、怎么理解 "注意力机制"。
- Transformer 中的 Attention 具体与怎样的。
- 注意力机制在结构和工程上的变体与优化。
通俗地理解:
Attention = 当前 token 根据上下文动态决定"看谁",再按权重汇总信息。
例如:
小明把书放进书包,因为它很重。
理解"它"时,人类会自然回看"书"和"书包",再根据"很重"判断它更可能指"书"。
模型也需要类似能力:处理某个 token 时,不只看自己或附近词,而是从上下文里读取相关信息。
1. 为什么需要注意力机制
语言不是一个只靠相邻词就能理解的序列。很多关系会跨越很长距离,也会随着上下文变化。
1.1 解决长距离依赖
看这句话:
那位昨天在会议上介绍新项目、并回答了很多问题的工程师,今天请假了。
"请假了"的主语是"工程师",但中间隔了很多词。如果模型只能强依赖相邻位置,就很难稳定抓住这种远距离关系。
Attention 的价值是:当前位置可以直接和上下文中的任意可见 token 建立联系。它不需要信息一步一步从相邻 token 传过来。
1.2 解决动态上下文问题
同一个词在不同句子里需要关注的对象不同。
张三把钥匙交给李四,因为他要出门。
张三把钥匙交给李四,因为他忘带钥匙了。
两句话里的“他”可能指向不同人。模型不能写死规则,只能根据当前上下文动态计算:哪些 token 更相关,应该从哪里读信息。
1.3 让每个 token 变成"带上下文的表示"
Embedding 只把 token 变成初始向量。这个初始向量本身还不知道句子里其他 token 的信息。
Attention 做的事是让 token 之间交换信息:
初始 token 向量
-> 读取上下文
-> 融合相关信息
-> 变成带上下文的 hidden state
所以 Transformer 里的 token 表示不是静态的。经过多层 Attention 后,“苹果”在“吃苹果”和“苹果公司发布芯片”里的内部表示会变得不同。
2. 什么是注意力机制
Attention 的核心不是“注意力”这个比喻,而是一个 加权汇总 操作。
2.1 最小定义
当模型处理当前 token 时,它会对上下文里的 token 计算一组权重:
| 当前 token | 关注对象 | 权重 |
|---|---|---|
| 它 | 小明 | 0.10 |
| 它 | 电脑 | 0.75 |
| 它 | 桌子 | 0.10 |
| 它 | 它自己 | 0.05 |
然后按权重汇总这些 token 提供的信息:
新的“它”的表示
= 0.10 × 小明的信息
+ 0.75 × 电脑的信息
+ 0.10 × 桌子的信息
+ 0.05 × 它自己的信息
权重越高,说明当前 token 从那个位置读取的信息越多。
2.2 Attention 在 Transformer 里的作用
在 Transformer Block 里,Attention 主要负责 token 之间的信息交互;FFN / MLP 主要负责对每个 token 的表示做进一步非线性加工。
简化理解:
Attention:token 之间传递信息
FFN / MLP:每个 token 内部加工信息
LayerNorm + Residual:让深层网络稳定训练
所以 Attention 不是整个 Transformer,但它是 Transformer 能处理上下文关系的核心部件。
2.3 Attention 并非固定规则
Attention 权重不是人工写好的规则,也不是固定词典。它由模型参数和当前输入共同计算出来。
同一个 token 在不同上下文里的注意力分布会变化:
他不饿 -> 可能关注“把蛋糕给别人”
他忘带钥匙 -> 可能关注“钥匙”和“收钥匙的人”
这也是它比简单关键词匹配更强的地方。
3. 怎么理解注意力机制
理解 Attention,重点是三件事:Q/K/V、三步计算、矩阵形式。
3.1 Q、K、V 的直觉
可以用“检索系统”来理解:
| 组件 | 直觉 | 作用 |
|---|---|---|
| Query(Q) | 我现在想找什么 | 当前 token 用它去匹配上下文 |
| Key(K) | 我有什么索引特征 | 每个 token 用它表示“我能被怎样找到” |
| Value(V) | 我真正提供什么内容 | 被注意力权重加权汇总的信息 |
注意力不是直接用 Q 去拿 K,而是:
Q 和 K 算相关性
相关性变成权重
根据权重去汇总 V
为什么 K 和 V 要分开?因为“怎么被检索到”和“真正提供什么内容”不是一回事。
例如一本书:
标题 / 标签 / 摘要:用来判断是否相关
正文内容:真正被读取的信息
对应到 Attention:
- Key 更像索引,用来匹配。
- Value 更像内容,用来汇总。
分开后,模型可以学习更灵活的关系:一个 token 用某种特征被找到,再用另一种信息影响当前 token。
3.2 Attention 的三步计算
标准 Attention 可以拆成三步。
第一步,计算相关性分数:
score = Q · K
例如当前 token 是“它”:
| Key 对象 | 分数 |
|---|---|
| 小明 | 1.0 |
| 电脑 | 4.0 |
| 桌子 | 0.5 |
| 它 | 0.2 |
第二步,用 softmax 将相关性分数变成权重:
[1.0, 4.0, 0.5, 0.2]
-> softmax
-> [0.04, 0.86, 0.02, 0.08]
第三步,用权重加权汇总 Value:
output
= 0.04 × V_小明
+ 0.86 × V_电脑
+ 0.02 × V_桌子
+ 0.08 × V_它
最后得到的 output 就是当前 token 融合上下文后的新表示。
3.3 Attention 公式
标准 Scaled Dot-Product Attention (缩放点积注意力) 写作:
逐段看:
| 片段 | 含义 |
|---|---|
| 每个 Query 和每个 Key 算相似度 | |
| 缩放因子,避免维度变大后分数过大 | |
softmax | 把分数变成权重 |
| 乘以 | 按权重汇总内容 |
公式是把上面提到的三步计算过程写成了矩阵计算形式。
3.4 注意力矩阵
注意力矩阵来自上面公式里的这一段:
也就是三步计算中的第二步:先用 算出相关性分数矩阵,再对每一行做 softmax,得到注意力权重矩阵。
假设有 3 个 token:
我 / 喜欢 / 苹果
先算出来的分数矩阵可能是:
| 当前 token \ 被关注 token | 我 | 喜欢 | 苹果 |
|---|---|---|---|
| 我 | 3.0 | 2.3 | 1.2 |
| 喜欢 | 1.1 | 2.0 | 1.5 |
| 苹果 | 0.8 | 2.1 | 2.4 |
对每一行做 softmax 后,得到 Attention 权重矩阵:
| 当前 token \ 被关注 token | 我 | 喜欢 | 苹果 |
|---|---|---|---|
| 我 | 0.6 | 0.3 | 0.1 |
| 喜欢 | 0.2 | 0.5 | 0.3 |
| 苹果 | 0.1 | 0.4 | 0.5 |
按行看:
- 每一行表示一个当前 token 的注意力分布。
- 每一列表示它从某个 token 读取多少信息。
- 每行权重通常经过 softmax,和为 1。
所以注意力矩阵不是最终答案,而是“用多少比例读取每个 Value”的权重表。它还要继续乘以 ,才会得到这一层 attention 的输出。
3.5 完整示例
下面用一个极简的二维向量例子,把注意力计算的完整流程走一遍。真实模型里的向量维度可能是几千维,这里只用 2 维方便手算。
假设有两个 token:
我 / 苹果
它们进入某一层 attention 后,已经得到各自的 Q、K、V:
| token | Q | K | V |
|---|---|---|---|
| 我 | [1, 0] | [1, 0] | [2, 0] |
| 苹果 | [1, 1] | [0, 1] | [0, 3] |
现在只看“苹果”这个当前 token 怎么更新自己的表示。
第一步,用"苹果"的 Query 去匹配所有 token 的 Key:
Q_苹果 = [1, 1]
score(苹果 -> 我) = [1, 1] · [1, 0] = 1
score(苹果 -> 苹果) = [1, 1] · [0, 1] = 1
这里两个分数一样,表示"苹果"对"我"和"苹果"自己的匹配程度相同。
第二步,对分数做 softmax,得到权重:
softmax([1, 1]) = [0.5, 0.5]
这就是"苹果"这一行的注意力权重:
| 当前 token | 关注"我" | 关注"苹果" |
|---|---|---|
| 苹果 | 0.5 | 0.5 |
第三步,用权重汇总 Value:
output_苹果
= 0.5 × V_我 + 0.5 × V_苹果
= 0.5 × [2, 0] + 0.5 × [0, 3]
= [1, 0] + [0, 1.5]
= [1, 1.5]
这个 [1, 1.5] 就是"苹果"经过这一层、这个 head 的 attention 后得到的新向量。它不再只包含"苹果"自己的信息,也混入了从"我"那里读取到的信息。
如果把两个 token 都一起算,矩阵形式就是:
QK^T -> 分数矩阵
softmax(分数矩阵) -> 注意力权重矩阵
注意力权重矩阵 × V -> 每个 token 的新向量
4. Transformer 中的注意力计算
前面的例子直接给出了 Q、K、V。
实际 Transformer 里,Q、K、V 不是凭空来的,而是由当前层的 hidden states 经过投影矩阵计算出来的。
假设输入到某一层 attention 的 hidden states 是:
X: [seq_len, hidden_size]
其中:
seq_len是当前序列长度。hidden_size是每个 token 的向量维度。X的每一行就是一个 token 在当前层开始时的表示。
4.1 参数和中间值
在进行下面的内容之前,首先我们应该区分哪些是模型可学习的、用于训练的参数,哪些只是一次前向计算中临时算出来的中间值。
参数是模型文件里保存的权重,会在训练中被更新,推理时固定使用。
| 名称 | 是否可学习 | 说明 |
|---|---|---|
| Token embedding table/matrix | 是 | 把 token id 映射成初始向量的查表矩阵 |
W_Q / q_proj.weight | 是 | 把 hidden state 投影成 Query |
W_K / k_proj.weight | 是 | 把 hidden state 投影成 Key |
W_V / v_proj.weight | 是 | 把 hidden state 投影成 Value |
W_O / o_proj.weight | 是 | 把多个 head 的输出混合回 hidden size |
| FFN / MLP 权重 | 是 | Attention 后继续加工每个 token 的表示 |
| LayerNorm 参数 | 通常是 | 缩放、平移 hidden state 的参数 |
中间值不是模型长期保存的知识,而是每次输入经过模型时临时计算出来的激活。
| 名称 | 是否可学习 | 说明 |
|---|---|---|
| token id | 否 | tokenizer 产生的离散编号 |
| token embedding 向量 | 否 | 用 token id 从 embedding table 查出来的结果 |
hidden state X | 否 | 某一层开始时,每个 token 当前的向量表示 |
| Q / K / V | 否 | X 经过 q_proj/k_proj/v_proj 后算出来的中间结果 |
| attention score | 否 | 得到的相关性分数 |
| attention weight | 否 | score 经过 mask 和 softmax 后得到的权重 |
| attention output | 否 | attention weight 乘以 V 后得到的输出 |
所以更准确地说:
可学习的是
- Embedding 层中的 embedding 矩阵
- Attention 层的一系列投影矩阵,如
q_proj、k_proj、v_proj、o_proj等 - FFN / MLP 层的权重
- Norm 层的参数
而 Q/K/V 矩阵、hidden state、注意力分数与权重是用这些参数算出来的中间值。
4.2 q_proj、k_proj、v_proj
Transformer 会先用 3 组可学习的线性投影矩阵 W_Q、W_K、W_V 把 X 分别变成 Q、K、V
在代码或模型结构里,这三组矩阵通常对应:
| 模块名 | 作用 |
|---|---|
q_proj | 生成 Query,表示当前 token 想找什么 |
k_proj | 生成 Key,表示每个 token 如何被匹配 |
v_proj | 生成 Value,表示每个 token 真正提供什么内容 |
如果暂时不考虑多头和 bias,形状可以简化理解为:
X: [seq_len, hidden_size]
W_Q: [hidden_size, hidden_size] -> Q: [seq_len, hidden_size]
W_K: [hidden_size, hidden_size] -> K: [seq_len, hidden_size]
W_V: [hidden_size, hidden_size] -> V: [seq_len, hidden_size]
所以 Q/K/V 本质上是同一批 token 表示 X 的三种不同投影结果。
4.3 多头与 reshape
实际 LLM 通常使用 Multi-Head Attention。也就是说,Q/K/V 生成后,还会被拆成多个 head。
例如:
hidden_size = 4096
num_attention_heads = 32
head_dim = 128
此时 Q 会从:
Q: [seq_len, hidden_size]
reshape 成:
Q: [num_heads, seq_len, head_dim]
K 和 V 也会做类似处理。每个 head 会独立执行一次:
这里的 、、 表示第 个 head 对应的那一份 Q/K/V。
4.4 attention mask
在 Causal Attention 中,mask 会加在 softmax 之前,也就是加在 attention score 上。
简化写法:
未来位置会被 mask 成一个极小值,softmax 后权重接近 0。这样模型就不能从未来 token 读取 Value。
4.5 o_proj
每个 head 都会输出一份结果:
head_1, head_2, ..., head_n
这些 head 的输出会先拼接:
concat(head_1, head_2, ..., head_n)
然后经过 W_O(或者叫 o_proj ):
W_O 的作用是把多个 head 的信息重新混合,并投影回模型的 hidden_size,这样后续的残差连接、LayerNorm 和 FFN 才能继续处理。
4.6 完整流程
总结一下 Transformer 中 Attention 机制的完整流程:
X
- (q_proj / k_proj / v_proj 投影) -> Q / K / V
- (reshape) -> 多个 head
- 每个 head
- (QK^T,加 mask) -> 注意力分数
- (softmax) -> 注意力权重
- (权重 × V) -> hidden state
- (concat 合并多个 head) -> hidden state
- (o_proj 投影) -> attention output
所以在真实 Transformer 里,Attention 不只是一个公式,而是一组可学习投影矩阵加上矩阵乘法、mask、softmax、head concat 和输出投影的完整计算模块。
5. Self、Causal、Multi-Head 等概念
从"信息从哪里来、能看哪里、分几个头看、K/V 怎么共享"来区分这几个概念。
5.1 Self-Attention 自注意力
Self-Attention 指 Query、Key、Value 都来自同一个序列。
例如:
猫坐在垫子上。
句子内部的 token 互相读取信息:
| 当前 token | 可能关注 |
|---|---|
| 猫 | 坐、垫子 |
| 坐 | 猫、垫子 |
| 垫子 | 坐、上 |
| 上 | 垫子 |
大语言模型里的核心注意力通常就是 Self-Attention:每个 token 从同一段上下文中读取信息。
5.2 Cross-Attention 交叉注意力
Cross-Attention 指 Query 来自一个序列,Key / Value 来自另一个序列。
典型例子是 Encoder-Decoder 翻译模型:
英文:The cat sat on the mat.
中文:猫坐在垫子上。
生成中文“猫”时,目标序列里的 Query 会去源序列里关注 cat;生成“垫子”时,会更关注 mat。
现代聊天 LLM 多是 Decoder-only,主要使用的是 Causal Self-Attention。但 Cross-Attention 这个概念仍然有助于我们理解 注意力就是对齐并读取相关信息。
5.3 Causal Attention 因果注意力
自回归模型(如GPT、LLaMA、Qwen 这类生成模型)的任务是:根据过去预测下一个 token。
训练句子:
我 喜欢 苹果
预测"喜欢"时,模型不能看到"苹果";预测"苹果"时,才能看到"我"和"喜欢"。
所以需要 causal mask 来掩盖到不应当看到的部分:
| 当前 token \ 可见 token | 我 | 喜欢 | 苹果 |
|---|---|---|---|
| 我 | 可见 | 不可见 | 不可见 |
| 喜欢 | 可见 | 可见 | 不可见 |
| 苹果 | 可见 | 可见 | 可见 |
Causal Attention 本质上仍然是 Self-Attention,只是加了"只能看过去和当前"的可见性限制。
5.4 Bidirectional Attention 双向注意力
与 Causal Attention 相对的概念是 Bidirectional Attention:
| 类型 | 可见范围 | 代表模型 | 适合任务 |
|---|---|---|---|
| Bidirectional Attention | 可以看左右上下文 | BERT | 理解、分类、Embedding |
| Causal Attention | 只能看当前位置之前 | GPT、LLaMA、Qwen | 生成、对话 |
例如:
我把苹果放在桌上,因为它很甜。
BERT 处理“它”时,可以同时看前后文。GPT 在生成到"它"时,只能看已经出现的前文。
5.5 Multi-Head Attention 多头注意力
Single-Head Attention 只能在一个表示空间里计算关系。Multi-Head Attention 会让模型并行使用多个 head,从不同角度读取上下文。
这里的 head 可以理解成“一组独立的注意力计算通道”。
更具体一点,在 Multi-Head Attention 中:
- 模型的 hidden state 会先被投影成 Q、K、V。
- Q、K、V 会按
num_attention_heads切成多份,每一份就是一个 attention head。 - 每个 head 都在自己的低维子空间里单独计算一套 attention 分数、权重和输出。
- 多个 head 的输出
concat起来,再经过o_proj混合回原来的 hidden size。
例如一个简化配置:
hidden_size = 4096
num_attention_heads = 32
head_dim = 4096 / 32 = 128
这里:一个 token 的 hidden state 被分到 32 个 head 中,每个 head 处理 128 维表示。每个 head 都没有使用完整数据,而是同一层 attention 里的一个子通道。
例如:
小王把蛋糕给了小李,因为他不饿。
不同 head 可能关注不同关系:
| Head | 可能关注 |
|---|---|
| Head 1 | “他”指代谁 |
| Head 2 | “给了”的动作关系 |
| Head 3 | “不饿”和“给蛋糕”的因果关系 |
| Head 4 | 名词之间的位置关系 |
简化流程:
输入 hidden states
- (q_proj / k_proj / v_proj) -> Q、K、V
-> reshape 成多个 head
-> 每个 head 独立计算 attention
-> concat 多个 head 的输出
-> o_proj 混合
-> 输出
多头的价值不是简单“多算几遍”,而是让模型可以同时学习多种关系。
5.6 MHA 变体
自回归模型在生成时,模型会一个 token 一个 token 往外生成。为了避免每一步都重新计算所有历史 token 的 Key 和 Value,推理框架会把历史 token 的 K/V 缓存起来,这个缓存就叫 KV Cache。
KV Cache 越大,推理显存压力越高。它的大小和 K/V head 数量、K/V 保存形式强相关,所以很多现代 LLM 会调整 K/V head 的数量,或者进一步压缩 K/V 的缓存表示。
下面是几类常见的 MHA 变体:
| 缩写 | 全名 | 中文名 | K/V head 关系 | 特点 |
|---|---|---|---|---|
| MHA | Multi-Head Attention | 多头注意力 | 每个 Query head 都有自己的 K/V head | 表达能力强,但 KV Cache 最大 |
| MQA | Multi-Query Attention | 多查询注意力 | 多个 Query head 共享一组 K/V head | KV Cache 小,推理省显存 |
| GQA | Grouped-Query Attention | 分组查询注意力 | 一组 Query head 共享一组 K/V head | 在效果和推理成本之间折中 |
| MLA | Multi-head Latent Attention | 多头潜在注意力 | 不直接缓存完整 K/V,而是缓存压缩后的 latent 表示,再还原或投影出 K/V | 进一步降低 KV Cache 压力,常见于追求长上下文和高吞吐的模型设计 |
简化理解:
MHA:Q head 多,K/V head 也多
MQA:Q head 多,K/V head 很少
GQA:Q head 分组,每组共享 K/V
MLA:不只减少 K/V head,而是压缩 K/V 的缓存表示
这也是看模型配置时要关注 num_attention_heads、num_key_value_heads 以及是否使用 MLA 这类 latent KV 设计的原因。它们都会影响 KV Cache 显存。
5.7 小结
不同的 Attention 的信息来源以及 Mask 类型应用在不同架构上:
| 架构类型 | Self / Cross | Mask | 典型模型 | 主要用途 |
|---|---|---|---|---|
| Encoder-Only | Self | Bidirectional | BERT 及变体 | 理解类: 分类、NER、embedding 等 |
| Decoder-Only | Self | Causal | GPT/ Claude/ LLaMA/ Qwen | 生成类: 对话、写作、代码生成等 |
| Encoder-Decoder | Self Encoder + Self & Cross Decoder | Non-mask Encoder + Causal Decoder | T5 / BART | Seq2Seq 类: 翻译、摘要、生成等 |
- 工程上为了压缩 MHA 的 KV Cache,发展出了 MHA 的各种变体如 MQA、GQA、MLA 等。
- 标准 Transformer 架构是 Encoder-Decoder 类型,使用的注意力是:
- Encoder:Self Bidirectional Multi-Head Attention
- Decoder:Self Causal Multi-Head Attention + Cross Multi-Head Attention (Non-causal)
6. 工程上的注意力优化
前面关注的是 Attention 的基本机制、常见结构及变体。
实际 LLM 部署时还会遇到更工程化的优化,比如 FlashAttention、PagedAttention、Sliding Window Attention、Sparse Attention 等技术。
这些方法不一定改变"Q 找 K、得到权重、加权汇总 V"这个核心逻辑,但会改变计算方式、内存组织方式或可见范围,从而提升速度、降低显存或支持更长上下文。
6.1 FlashAttention
FlashAttention 主要优化的是 attention 的计算和显存读写。
标准 attention 如果直接生成完整的注意力矩阵,会产生很大的中间结果。FlashAttention 的思路是分块计算 attention,减少中间矩阵在显存中的读写,让 GPU 更高效地完成同样的数学计算。
简化理解:
普通 attention:更容易产生大的中间注意力矩阵
FlashAttention:分块计算,减少显存读写,提高计算效率
它通常不改变模型语义,主要是让 attention 算得更快、更省显存。
6.2 PagedAttention
PagedAttention 主要优化的是 KV Cache 的管理方式。
自回归推理中,每个请求都要保存历史 token 的 K/V。如果直接为每个请求预留连续大块显存,很容易浪费或碎片化。PagedAttention 借鉴操作系统分页的思路,把 KV Cache 切成小块按需分配。
简化理解:
普通 KV Cache 管理:容易预留过多或产生碎片
PagedAttention:把 KV Cache 分页,按需分配和调度
vLLM 的高吞吐能力很大程度上就来自这类 KV Cache 分页管理和调度策略。
6.3 Sliding Window Attention
Sliding Window Attention 会限制每个 token 只关注附近窗口内的 token,而不是关注完整上下文。
例如窗口大小是 4096,那么当前位置可能只看最近 4096 个 token。这样可以降低长上下文下的 attention 计算和 KV Cache 压力。
代价也很明显:窗口外的信息不能被当前 token 直接读取,需要模型结构或系统策略额外处理远距离信息。
6.4 Sparse Attention
Sparse Attention 不让每个 token 都关注所有 token,而是只关注一部分位置。
常见思路包括:
- 局部关注:只看附近 token。
- 全局 token:少数特殊 token 可以被很多位置关注。
- 间隔关注:按固定间隔看部分远处 token。
它的目标是减少完整 attention 带来的成本。代价是实现更复杂,而且不同稀疏模式会影响模型能读取哪些信息。
6.5 与 MHA 及其变体的关系
这些概念容易混在一起,可以这样分:
| 类型 | 主要解决什么 |
|---|---|
| MHA / MQA / GQA / MLA | K/V head 数量怎么设计,存储是完整存、共享还是压缩,影响表达能力和 KV Cache 大小 |
| FlashAttention | attention 计算怎么更快、更省显存读写 |
| PagedAttention | KV Cache 怎么分配和管理 |
| Sliding Window / Sparse Attention | token 可见范围怎么限制,降低长上下文成本 |
所以 MHA、MQA、GQA 更像模型结构设计;FlashAttention、PagedAttention 等更像推理和系统实现优化。
7. 常见问题
7.1 Attention 和 KV Cache 是什么关系
KV Cache 是 Attention 在自回归推理中的缓存机制。
生成模型推理时是一个个 token 往外生成。如果每生成一个新 token,都重新计算所有历史 token 的 Key 和 Value,会出现重复计算导致浪费的情况。
KV Cache 的做法是:
历史 token 的 K/V 已经算过 -> 存起来
新 token 只计算当前 Q/K/V
当前-Q 和 历史-K 计算注意力权重
再用权重汇总 历史-V
把新 token 的 K/V 追加进 cache
所以 KV Cache 本质上是一个典型的 用空间换时间 策略:多占用一部分显存来保存历史 K/V,换取 decode 阶段少做大量重复计算。
而二者的关系可以概括为:
- Attention 是 算法架构层 的结构机制:用 Q/K/V 计算权重并汇总信息。
- KV Cache 是 模型推理层 的优化机制:缓存历史 K/V,用显存占用换取更少的重复计算。
- KV Cache 不会改变 Attention 的数学含义,只是让 decode 阶段更高效。
7.2 为什么 KV Cache 会占用显存
KV Cache 保存的是每一层、每个历史 token 的 Key 和 Value。
简化看:
KV Cache 大小
≈ 层数 × token 数 × KV head 数 × head_dim × 2 × 每元素字节数
这里的 2 表示 Key 和 Value 各一份。
所以 KV Cache 会随着这些因素变大:
| 因素 | 影响 | 备注 |
|---|---|---|
| 输入/上下文 越长 | 每个请求缓存的 token 越多 | |
| 输出越长 | 生成过程中 cache 继续增长 | |
| 并发越高 | 同时维护多份请求的 cache | |
| KV head 越多 | 每个 token 要存更多 K/V | MQA,GQA 即是针对这里进行优化 |
| 精度越高 | 每个元素占用字节更多 |
这就是为什么长上下文和高并发服务里,显存瓶颈经常不是模型权重,而是 KV Cache。
更完整的工程解释可以看:KV Cache。
7.3 Attention 为什么让长上下文变贵
Self-Attention 需要计算 token 之间的关系。
如果序列长度是 ,完整注意力矩阵大致是 :
| 序列长度 | 注意力关系数量 |
|---|---|
| 1,000 | 1,000,000 |
| 10,000 | 100,000,000 |
| 100,000 | 10,000,000,000 |
实际系统会用 FlashAttention、PagedAttention、KV Cache、滑动窗口、稀疏注意力等优化,但基本事实仍然存在:上下文越长,注意力相关的计算、访存和缓存压力越高。
7.4 Attention 和位置编码是什么关系
Self-Attention 本身只看 token 之间的相似度,不天然知道顺序。
如果没有位置信息,模型很难区分:
小明打了小王
小王打了小明
所以 Transformer 需要位置编码或类似机制。现代 Decoder-only LLM 中常见 RoPE,它会在 Attention 计算中对 Query 和 Key 注入位置信息。
更完整解释可以看:位置编码。
7.5 Attention 权重能解释模型为什么这么回答吗
只能作为参考,不能等同于完整解释。
原因是:
- 模型有很多层,每层都有不同 attention。
- 每层有多个 head。
- FFN / MLP 会继续加工信息。
- 残差连接会混合不同路径的信息。
模型最终输出由整个网络共同决定。
所以更准确的说法是:
Attention 权重可以帮助观察某层某头读了哪里,但不能完整解释模型为什么输出某个答案。
8. 常见误区
注意力就是找关键词
不只是。Attention 可以关注关键词,也可以关注语法、指代、位置、格式、因果关系、代码变量、函数定义等更抽象的关系。
权重最高的 token 一定最重要
不一定。单层单头的注意力权重只是模型内部很多路径中的一部分。最终输出还会经过多层、多头、FFN、残差连接和 LM Head。
注意力越集中越好
不一定。有些任务需要集中关注一个证据,有些任务需要综合多个信息。过度集中可能导致模型忽略必要上下文。
长上下文就是无限记忆
不是。长上下文只是允许模型看到更多 token,不保证模型能稳定利用所有信息。上下文越长,也可能带来注意力干扰、成本上升和 KV Cache 压力。
KV Cache 会让模型“记住用户”
不会。KV Cache 是一次推理请求内部的临时缓存,保存的是当前上下文中 token 的 K/V。请求结束后通常会释放。它不是长期记忆,也不是训练参数更新。
Multi-Head 就是多个模型投票
不是。多个 head 是同一层内部的多个表示子空间,最后会 concat 并通过 o_proj 混合。它不是多个独立模型,也不是显式投票机制。
总结
Attention 的核心可以压缩成一句话:Q 结合 K,得到权重后加权汇总 V。
再展开一点:
- Attention 解决的是 token 如何从上下文中动态读取相关信息。
- Self-Attention 是同一序列内部 token 互相读取信息。
- Causal Attention 是加了 mask 来限制的自回归注意力。
- Multi-Head Attention 让模型从多个表示角度并行读取上下文。
- KV Cache 是 Attention 在推理阶段的缓存优化,核心是复用历史 Key 和 Value。
- MQA / GQA 通过减少或共享 K/V head,MLA 通过压缩表示来降低 KV Cache 成本。
- 推理部署中会使用 FlashAttention、PagedAttention、Sliding Window Attention 等技术进行 KV Cache 方面的工程优化。
todo: 其它 Attention 算法
大模型厂商在模型研发过程中,发展出各种各样注意力算法来提高模型表现或者效率:
- FLA
- Local Attention
- Linformer Attention
- Mamba