当前位置: 首页 > news >正文

Attention

(更好的阅读体验参考小红书同名)

 

Attention

1 Attention 介绍

为什么点积注意力要除以 ?

Attention的时间复杂度?

2 Transformer中的Attention

2.1 Encoder和Decoder中的Self-Attention

2.2 Decoder中的Cross-Attention

3 KV Cache键值缓存

3.1 KV Cache原理

4 MHA、MQA、GQA

4.1 MHA(Multi-head Attention,多头注意力)

MHA中的Attention Mask要怎么加?

4.2 MQA(Multi-query Attention,多查询注意力)

4.3 GQA(Grouped-query Attention,分组查询注意力)

S接着之前发布的《大模型学习路线(一):Transformer架构篇》,这次主要梳理一下 Attention 机制,力求让大家在面试中不仅能回答相关问题,也能体现出对 Attention 机制的理解。

1 Attention 介绍

Attention的核心思想是在处理序列数据时,网络应该更关注输入中的重要部分,而忽略不重要的部分,通过学习不同部分的权重,将输入的序列中的重要部分显式地加权,从而使得模型可以更好地关注与输出有关的信息。

传统的循环神经网络(RNN)或卷积神经网络(CNN,一般是1D-CNN)在处理整个序列时,难以捕捉到序列中不同位置的重要程度,可能导致信息传递不够高效,特别是在处理长序列时表现更明显。

Transformer 中使用的 Scaled Dot-Product Attention 缩放点积注意力公式如下,其中 Q 和 K 用来产生重要性权重,然后加权 V 做聚合输出

计算过程如下图:

Attention 思想理解:对当前 token 来说,输入序列里不同位置的重要性是不一样的,模型需要把更多注意力放在和当前任务最相关的信息上。

在这个过程中,Query 可以理解为“我现在想找什么信息”,Key 表示“我这里有什么信息”,Value 则是“真正要被取出的内容”。当前 token 会用自己的 Query 和所有 token 的 Key 做相似度计算,得到一组权重;相似度越高,说明对应 token 和当前 token 越相关,它的 Value 就会被赋予更大的权重。最终,模型通过对这些 Value 加权求和,得到融合上下文信息后的表示。

为什么点积注意力要除以 ?

除以 是为了防止高维下点积过大导致 softmax 饱和,本质上是在稳定方差、保证梯度和训练稳定性。

Attention的时间复杂度?

Attention的时间复杂度为 ,NM为序列长度,它要对序列中的任意两个向量都要计算相关度,得到一个 大小的相关度矩阵

2 Transformer中的Attention

2.1 Encoder和Decoder中的Self-Attention

Q、K、V 来自同一条输入序列,只希望关注同一序列内各节点之间的依赖信息查询、键和值都出自同一个输入序列,目标是捕捉序列内部的依赖关系
在 Transformer 的编码器(Encoder)和解码器(Decoder)的每一层里都包含 Self-Attention,它允许输入序列的每个部分去关注序列中的其他部分。二者差别在于:

  • Encoder 的 self-attention:当前位置的 token 与整条序列的所有 token 计算注意力;
  • Decoder 的 self-attention:当前位置的 token 只与它之前的 token 计算(Masked / Casual Attention),以避免解码时的信息泄漏。

伪代码如下

Python
1 获取输入的原始数据 X = input
2 转换为 Embedding 并加上位置信息编码
X = emb(X) + pe(X)
3 由 X 生成 Q, K, V
Q = QLinear(X)
K = KLinear(X)
V = VLinear(X)
4 计算 Attention 输出
Attention = softmax(QKᵀ / sqrt(d)) * V

2.2 Decoder中的Cross-Attention

Q、K、V 来自不同序列查询(Q)取自 Decoder 的当前输入序列,而键和值(K、V)来自 Encoder 的输出序列
Cross-Attention 让 Decoder 能够“关注”Encoder 的表示,这在机器翻译等场景尤为重要,例如将源语言序列的片段与目标语言序列对齐
对 Decoder 来说,第一个 attention 模块是 Masked Self-Attention,第二个则是 Cross-Attention

伪代码如下

Python
1 获取 Decoder 输入原始数据 X = input
2 转换为 Embedding 并加位置信息
X = emb(X) + pe(X)
# K, V 来自 Encoder 输出;Q 来自 X
3 生成 Q
Q = QLinear(X)
直接使用 Encoder 输出作为 K, V
4 计算 Attention 输出
Attention = softmax(QKᵀ / sqrt(d)) * V

3 KV Cache键值缓存

一句话总结:k和v指的分别是attention机制中的key和value的状态值,kv cache只出现在transformer结构的自回归的decoder中,其是为了避免scaled dot-product attention过程中的重复计算。

自回归 (Autoregressive):是一种生成策略。它定义了模型应该“做什么”——像链条一样,用上一步的输出作为下一步的输入,一个接一个地生成内容。这种方式保证了内容的相关性和流畅性。

3.1 KV Cache原理

KVCache:主流的自回归LLM所用的Casual Attention,在token by token递归推理生成时,每次计算当前token的attention时,都要用到序列前面的所有token的K和V。为了避免重新计算一遍这部分,可以将之前序列token计算过的KV缓存下来用

例如,我们要生成“I love Zhejiang University”这句话·,首先只有开头标记 <s>,计算过程如下图所示(为了便于理解,我们将 softmax 和 scale 去掉):

最终第一步的注意力计算公式为:

此时序列中的词为 "<s> I",由于有 Mask 机制,第二步计算如下图所示:

第二步的注意力为:

设 是第一行, 是第二行,有:

继续计算第三步:

计算注意力,有:

此时可以得出结论:

  1. 朴素的Attention计算存在大量冗余;
  2. 只与 有关,预测词 仅依赖于 ;
  3. 和 全程参与计算,可以缓存起来;
  4. 虽然叫做KV-Cache,但真正优化掉的是冗余的Q和Attn。

缺点:核心思路是利用空间换时间,当序列很长时,KV-Cache可能会出现内存爆炸的情况。

4 MHA、MQA、GQA

4.1 MHA(Multi-head Attention,多头注意力)

在原始Transformer模型中,注意力机制采用了多头注意力(MHA)的策略。它将模型的嵌入维度(embed_size)均分给多个注意力头(Head),使得每个头的维度(head_size)为 embed_size / num_heads。这种结构类似于分组卷积(Group Convolution),通过建立多个并行的信息通道,让每个头能够专注于输入序列中不同表示子空间的特征,从而捕获到更加丰富和多样化的信息。

代码如下:

Python
class MHA(nn.Module):
def __init__(self, embed_size, num_heads):
super().__init__()

self.num_heads = num_heads
self.embed_size = embed_size
self.head_dim = embed_size // num_heads

self.Q_linear = nn.Linear(embed_size, embed_size)
self.K_linear = nn.Linear(embed_size, embed_size)
self.V_linear = nn.Linear(embed_size, embed_size)
self.O_linear = nn.Linear(embed_size, embed_size)

def forward(self, x, attn_mask):
# x: [B, L, H]
# attn_mask: [B, L, L]

B, L, H = x.shape # H = embed_size

q, k, v = self.Q_linear(x), self.K_linear(x), self.V_linear(x)
# q, k, v: [B, L, H]

q = q.reshape(B, L, self.num_heads, self.head_dim)
k = k.reshape(B, L, self.num_heads, self.head_dim)
v = v.reshape(B, L, self.num_heads, self.head_dim)
# q, k, v: [B, L, num_heads, head_dim]

q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# q, k, v: [B, num_heads, L, head_dim = H // self.num_heads]

attn_map = q @ k.transpose(-2, -1) / math.sqrt(self.head_dim)
# attn_map: [B, num_heads, L, head_dim] @ [B, num_heads, head_dim, L]
# = [B, num_heads, L, L]

if attn_mask is not None:
attn_mask = attn_mask.reshape(B, 1, L, L)
attn_map = attn_map.masked_fill(attn_mask == 0, float('-inf') )

attn_probs = torch.softmax(attn_map, dim=-1)
# attn_probs: [B, num_heads, L, L]

attn_output = attn_probs @ v
# attn_output: [B, num_heads, L, L] @ [B, num_heads, L, head_dim]
# = [B, num_heads, L, head_dim]

attn_output = attn_output.transpose(1, 2).reshape(B, L, H)
# [B, num_heads, L, head_dim] -> [B, L, num_heads, head_dim] -> [B, L, H]

attn_output = self.O_linear(attn_output)

return attn_output

MHA中的Attention Mask要怎么加?

在 MHA 中,Attention Mask 是加在 attention score 上的,也就是:之后、softmax 之前

作用是把 不该关注的位置屏蔽掉。常见有两种:

- Padding Mask:屏蔽 PAD token

- Causal Mask:屏蔽未来 token

实现方式通常是把这些位置的 score 设为 -inf:

Python
scores = scores.masked_fill(mask == 0, float('-inf'))

4.2 MQA(Multi-query Attention,多查询注意力)

所有的query head共享KV。

缺点:MQA对KV Cache的压缩太严重,会影响模型的学习效率和最终效果。

代码如下

Python
class MQA(nn.Module):
def __init__(self, embed_size, num_heads):
super().__init__()

self.num_heads = num_heads
self.embed_size = embed_size
self.head_dim = embed_size // num_heads

self.Q_linear = nn.Linear(embed_size, embed_size)
self.K_linear = nn.Linear(embed_size, self.head_dim)
self.V_linear = nn.Linear(embed_size, self.head_dim)
self.O_linear = nn.Linear(embed_size, embed_size)

def forward(self, x, attn_mask):
# x: [B, L, H]
# attn_mask: [B, L, L]

B, L, H = x.shape # H = embed_size

q, k, v = self.Q_linear(x), self.K_linear(x), self.V_linear(x)
# q: [B, L, H]
# k, v: [B, L, head_dim]

q = q.reshape(B, L, self.num_heads, self.head_dim)
k = k.reshape(B, L, 1, self.head_dim).repeat(1, 1, self.num_heads, 1)
v = v.reshape(B, L, 1, self.head_dim).repeat(1, 1, self.num_heads, 1)
# q, k, v: [B, L, num_heads, head_dim]

q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# q, k, v: [B, num_heads, L, head_dim = H // self.num_heads]

attn_map = q @ k.transpose(-2, -1) / math.sqrt(self.head_dim)
# attn_map: [B, num_heads, L, head_dim] @ [B, num_heads, head_dim, L]
# = [B, num_heads, L, L]

if attn_mask is not None:
attn_mask = attn_mask.reshape(B, 1, L, L)
attn_map = attn_map.masked_fill(attn_mask == 0, float('-inf') )

attn_probs = torch.softmax(attn_map, dim=-1)
# attn_probs: [B, num_heads, L, L]

attn_output = attn_probs @ v
# attn_output: [B, num_heads, L, L] @ [B, num_heads, L, head_dim]
# = [B, num_heads, L, head_dim]

attn_output = attn_output.transpose(1, 2).reshape(B, L, H)
# [B, num_heads, L, head_dim] -> [B, L, num_heads, head_dim] -> [B, L, H]

attn_output = self.O_linear(attn_output)

return attn_output

4.3 GQA(Grouped-query Attention,分组查询注意力)

将query分为g组,每组的query共享KV,平衡效果和缓存。

代码如下

Python
class GQA(nn.Module):
def __init__(self, embed_size, num_q_heads, num_kv_heads):
super().__init__()

self.num_q_heads = num_q_heads
self.num_kv_heads = num_kv_heads
self.embed_size = embed_size
self.head_dim = embed_size // num_q_heads

self.Q_linear = nn.Linear(embed_size, embed_size)
self.K_linear = nn.Linear(embed_size, self.head_dim * self.num_kv_heads)
self.V_linear = nn.Linear(embed_size, self.head_dim * self.num_kv_heads)
self.O_linear = nn.Linear(embed_size, embed_size)

def forward(self, x, attn_mask):
# x: [B, L, H]
# attn_mask: [B, L, L]

B, L, H = x.shape # H = embed_size

q, k, v = self.Q_linear(x), self.K_linear(x), self.V_linear(x)
# q: [B, L, H]
# k, v: [B, L, head_dim * num_kv_heads]

q = q.reshape(B, L, self.num_q_heads, self.head_dim)
k = k.reshape(B, L, self.num_kv_heads, self.head_dim).repeat(1, 1, self.num_q_heads // self.num_kv_heads, 1)
v = v.reshape(B, L, self.num_kv_heads, self.head_dim).repeat(1, 1, self.num_q_heads // self.num_kv_heads, 1)
# q, k, v: [B, L, num_q_heads, head_dim]

q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# q, k, v: [B, num_heads, L, head_dim = H // self.num_heads]

attn_map = q @ k.transpose(-2, -1) / math.sqrt(self.head_dim)
# attn_map: [B, num_heads, L, head_dim] @ [B, num_heads, head_dim, L]
# = [B, num_heads, L, L]

if attn_mask is not None:
attn_mask = attn_mask.reshape(B, 1, L, L)
attn_map = attn_map.masked_fill(attn_mask == 0, float('-inf') )

attn_probs = torch.softmax(attn_map, dim=-1)
# attn_probs: [B, num_heads, L, L]

attn_output = attn_probs @ v
# attn_output: [B, num_heads, L, L] @ [B, num_heads, L, head_dim]
# = [B, num_heads, L, head_dim]

attn_output = attn_output.transpose(1, 2).reshape(B, L, H)
# [B, num_heads, L, head_dim] -> [B, L, num_heads, head_dim] -> [B, L, H]

attn_output = self.O_linear(attn_output)

return attn_output

http://www.jsqmd.com/news/657470/

相关文章:

  • 从零开始:BepInEx游戏插件框架完全实战指南
  • AI写代码=埋雷?揭秘2024年83%生成代码含安全缺陷的惊人数据及3步加固法
  • 鸿蒙App开发实战:一键拉起高德/百度地图导航(附完整代码与避坑指南)
  • 从VS Code到JetBrains,智能代码生成插件选型对比,12项性能指标实测数据曝光
  • 大模型简明八股——Attention
  • 2998基于单片机的司机乘客酒驾检测系统设计(TLC1543)
  • Noto字体完全指南:如何为全球900+语言消除“豆腐块“显示问题
  • 智能代码生成效率提升300%:从Prompt设计到模型微调的5步实战闭环
  • 深入解析CRC校验:从数学原理到硬件实现
  • 2026届必备的十大降AI率助手推荐榜单
  • 2025届学术党必备的五大AI写作平台实际效果
  • 大模型简明八股——FFN, Residual Addition, LN
  • 知识图谱+LLM:解锁数据价值的黄金组合,企业智能决策的必经之路!
  • OpenVINO模型量化指南:从FP32到INT8的性能提升实测与避坑经验分享
  • SukiUI深度解析:如何为AvaloniaUI构建现代化桌面应用界面
  • 2026中国AI CRM选型全攻略:四大维度看清谁是真AI原生
  • 2999基于单片机的四字语音播放器设计
  • 前端开发者学 .NET:零基础到部署上线
  • 从程序员到AI大模型专家:一份超全转行攻略与学习资源大放送!
  • OCR数据集全攻略:从COCO-TEXT到SCUT-CTW1500,如何选择适合你的语言识别任务
  • PLL锁相环中的locked信号:如何用它实现可靠的系统复位(附Verilog代码示例)
  • 【笔试真题】- 阿里系列-2026.04.15-算法岗
  • 夸克如何永久免费扩容+领取1T空间容量教程
  • 【大厂内部未公开】智能代码生成Context理解失效诊断手册:覆盖IDE插件、CI流水线、PR辅助三大高危场景
  • OpenClaw v2026.4.15 深度解读剖析:从“工程极致”到“感知智能”与“可控韧性”的范式跃迁
  • 2026 年优质书法培训推荐榜:汲古堂书法高考培训领衔,聚焦书法统考、书法校考、书法高考培训精品机构 - 海棠依旧大
  • Ubuntu Server无桌面环境,如何搞定校园网深澜(Srun)认证?一个命令行工具全搞定
  • AI写代码不再“耍花招”:7步将GitHub Copilot深度嵌入CI/CD流水线(含Jenkins+GitLab CI实测配置清单)
  • 医学影像AI进阶:如何用UNet3+的‘全尺度’思想优化你的分割模型?不止于肝脏和脾脏
  • GEE实战:基于哨兵2号SR数据的地表反射率年度合成与批量导出