深入理解Attention机制:从Q、K、V到Multi-Head
深入解析 Attention(注意力机制)—— Transformer 的核心引擎。用数据库查询的类比,彻底理解 Q、K、V 的含义,掌握 Multi-Head Attention 的实现,并澄清 Softmax 与 RMSNorm 的常见混淆
本文是 MiniMind 学习系列的第3篇,深入解析 Attention(注意力机制)—— Transformer 的核心引擎。我们将用数据库查询的类比,让你彻底理解 Q、K、V 的含义,掌握 Multi-Head Attention 的实现,并澄清 Softmax 与 RMSNorm 的常见混淆。
关于本系列#
MiniMind ↗ 是一个简洁但完整的大语言模型训练项目,包含从数据处理、模型训练到推理部署的完整流程。我在学习这个项目的过程中,将核心技术点整理成了 minimind-notes ↗ 仓库,并产出了这个4篇系列博客,系统性地讲解 Transformer 的核心组件。
本系列包括:
- 归一化机制 - 为什么需要RMSNorm
- RoPE位置编码 - 如何让模型理解词序
- Attention机制(本篇)- Transformer的核心引擎
- FeedForward与完整架构 - 组件如何协同工作
一、引言#
1.1 Transformer 的灵魂#
如果说 Transformer 是一座大厦:
- **归一化(RMSNorm)**是地基 — 稳定训练
- **位置编码(RoPE)**是坐标系 — 区分位置
- Attention 是核心引擎 — 理解语义 ⭐
没有 Attention,Transformer 就不存在。
1.2 本文要解答的问题#
- Q、K、V 到底是什么?(不是玄学!)
- 为什么要分成 8 个 Head?
- Softmax 和 RMSNorm 有什么区别?(常见混淆)
- Attention 如何与 RoPE 配合工作?
- Multi-Head 的维度变化是怎样的?
1.3 适合读者#
- 听说过 Attention 但不理解计算细节
- 想从代码层面掌握 Multi-Head 机制
- 准备实现自己的 Transformer
- 对数学推导不恐惧(本文会详细解释)
二、Attention 的本质:词与词的相关性#
2.1 核心问题#
“在理解一个词时,应该关注句子中的哪些其他词?”
例子:
句子: "小明很喜欢他的猫,它总是在窗边睡觉"
当模型理解"它"这个词时:
"它" ← "小明" 相关性: 0.1 (可能性低,代词通常不指人名)
"它" ← "喜欢" 相关性: 0.05 (几乎无关)
"它" ← "猫" 相关性: 0.8 (高度相关!)✅
"它" ← "窗边" 相关性: 0.05 (几乎无关)
最终理解"它"的表示 = 0.1×[小明] + 0.05×[喜欢] + 0.8×[猫] + 0.05×[窗边]
≈ 主要来自"猫"的信息plaintextAttention 做的事情:
- 计算相关性分数(每两个词之间)
- 归一化成概率分布(Softmax,加起来 = 1)
- 加权求和(融合上下文)
2.2 输入输出对比#
# 输入:孤立的词向量(每个词不知道上下文)
input = [
[我的768维向量], # 不知道后面是"爱"还是"恨"
[爱的768维向量], # 不知道主语是谁、宾语是谁
[编程的768维向量] # 不知道是被爱还是被恨
]
# Attention 处理
# 输出:融合了上下文的词向量
output = [
[我的新向量], # 现在知道:我在"爱"这个动作中是主语
[爱的新向量], # 现在知道:连接"我"和"编程"
[编程的新向量] # 现在知道:在"爱"这个动作中是宾语
]python2.3 Self-Attention vs Cross-Attention#
Self-Attention(MiniMind 使用):
# 句子关注"自己内部"的词
sentence = "我爱编程"
# 计算:我 ← → 爱 ← → 编程 的相关性pythonCross-Attention(翻译模型使用):
# 句子 A 关注句子 B
chinese = "我爱编程"
english = "I love programming"
# 计算:"我" ← "I","爱" ← "love","编程" ← "programming"python为什么叫”Self”?
- 因为计算的是同一个句子内部的关系
- 不是”token 与自己”(虽然也会计算 q_i · k_i)
三、Q、K、V 详解:数据库查询类比#
3.1 经典类比:数据库查询#
理解 Q、K、V 最好的方式是类比 SQL 查询:
SELECT value ← 返回 Value
FROM memory_bank ← 记忆库(所有词)
WHERE key MATCHES query ← Key 匹配 Querysql对应关系:
| SQL 概念 | Attention 概念 | 作用 | 类比 |
|---|---|---|---|
| Query | Query (Q) | “我想查询什么信息?“ | 搜索条件 |
| Key | Key (K) | “我这里有什么信息?“ | 索引标签 |
| Value | Value (V) | “我的实际内容” | 数据值 |
3.2 具体例子:理解”爱”#
句子:“我 爱 编程”
当理解”爱”这个词时:
-
Query(“爱”想知道什么):主语和宾语是谁?我在表达什么动作?
-
Keys(其他词提供什么信息):
- Key(“我”) = “我是主语,第一人称代词”
- Key(“编程”) = “我是宾语,表示活动”
-
计算相似度:
- “爱”的Q · “我”的K = 0.6(中等相关)
- “爱”的Q · “编程”的K = 0.8(高度相关!)
-
Softmax归一化:
[0.25, 0.15, 0.60](关注”编程”60%) -
加权求和Value:
- “爱”的新表示 = 0.25×Value(“我”) + 0.15×Value(“爱”) + 0.60×Value(“编程”)
- 融合了上下文,知道自己连接”我”和”编程”
3.3 Q、K、V 怎么得到?#
关键发现:Q、K、V 都是从同一个输入 X 通过不同权重矩阵变换得到!
# 输入 X: [3, 768](3个词,每个768维)
# 权重矩阵 W_Q, W_K, W_V: [768, 768]
Q = X @ W_Q # Query: "我想知道什么?"
K = X @ W_K # Key: "我有什么信息?"
V = X @ W_V # Value: "我的实际内容"python维度相同,含义不同。三个矩阵将输入变换成三个不同”视角”。
3.4 权重矩阵的本质#
常见疑问:“W_Q、W_K、W_V 从哪来?”
答案:
- 是什么:神经网络的可学习参数
- 怎么来:通过训练数据反向传播学习
- 存在哪里:保存在模型文件里(.pth, .safetensors)
- 作用:把输入变换成三个不同”视角”
在 MiniMind 中,它们是三个 nn.Linear 层(q_proj, k_proj, v_proj)。训练后,W_Q 学会提取”查询特征”,W_K 学会提取”索引特征”,W_V 学会提取”内容特征”。
四、Attention 计算流程#
4.1 完整公式#
Attention(Q, K, V) = softmax(Q @ K^T / √d_k) @ Vplaintext这个公式浓缩了整个 Attention 机制!
4.2 分步骤详解#
步骤 1:计算相似度(点积)
scores = Q @ K.T # [seq_len, seq_len]
# scores[i, j] = Q[i] · K[j](两个向量越相似,点积越大)python步骤 2:缩放(除以 √d_k)
scaled_scores = scores / math.sqrt(head_dim)python为什么缩放?维度越大,点积越大。不缩放会导致 Softmax 太”尖锐”,梯度消失。缩放后分布更平滑,梯度更稳定。
步骤 3:Softmax 归一化
attn_weights = softmax(scaled_scores, dim=-1)python转换成概率分布:所有权重≥0,每一行加起来=1,可以解释为”关注度”。
步骤 4:加权求和 Value
output = attn_weights @ Vpython例如:“爱”的新表示 = 0.29×Value(“我”) + 0.36×Value(“爱”) + 0.25×Value(“编程”)
4.3 完整代码实现#
def attention(Q, K, V, mask=None):
head_dim = Q.shape[-1]
# 1-2. 计算相似度并缩放
scores = (Q @ K.transpose(-2, -1)) / math.sqrt(head_dim)
# 3. 应用掩码(可选,用于因果注意力)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# 4-5. Softmax + 加权求和
attn_weights = F.softmax(scores, dim=-1)
output = attn_weights @ V
return output, attn_weightspython五、Multi-Head Attention#
5.1 为什么需要多头?#
单头的局限:只能关注一个方面。
句子:“小明在北京的清华大学学习人工智能”
单头 Attention 可能只关注:
- 主谓宾关系(语法)
但我们希望同时关注:
- 语法结构(主谓宾)
- 实体关系(小明-清华)
- 地理位置(清华-北京)
- 主题领域(人工智能)
- 语义相关(学习-人工智能)
- …
解决方案:Multi-Head Attention!
5.2 “多副眼镜”的类比#
Head 1: 语法眼镜 👓
→ 关注主谓宾关系、句法结构
Head 2: 实体眼镜 🕶️
→ 关注人名、地名、机构名
Head 3: 语义眼镜 👓
→ 关注同义词、相关概念
Head 4: 长距离依赖眼镜 🕶️
→ 关注距离较远但相关的词
Head 5: 情感眼镜 👓
→ 关注情感词、态度词
...
Head 8: 主题眼镜 🕶️
→ 关注主题和领域词汇
最后:摘下所有眼镜,融合 8 个视角!plaintext5.3 Multi-Head 的实现流程#
# MiniMind 配置
hidden_size = 768
num_heads = 8
head_dim = hidden_size // num_heads = 96
# 完整流程
输入 X: [batch, seq_len, 768]
↓
生成 Q, K, V: [batch, seq_len, 768]
↓
拆分成 8 个头: [batch, seq_len, 8, 96]
↓
转置: [batch, 8, seq_len, 96] # 方便并行计算
↓
每个头独立计算 Attention(并行)
↓
输出: [batch, 8, seq_len, 96]
↓
转回: [batch, seq_len, 8, 96]
↓
合并(reshape): [batch, seq_len, 768]
↓
输出投影: [batch, seq_len, 768]python5.4 代码实现#
class MultiHeadAttention(nn.Module):
def __init__(self, hidden_size=768, num_heads=8):
super().__init__()
self.num_heads = num_heads
self.head_dim = hidden_size // num_heads # 96
self.q_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.k_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.v_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.o_proj = nn.Linear(hidden_size, hidden_size, bias=False)
def forward(self, x, mask=None):
batch, seq_len, _ = x.shape
# 1. 生成Q、K、V并拆分成多头
Q = self.q_proj(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
K = self.k_proj(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
V = self.v_proj(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# [batch, num_heads, seq_len, head_dim]
# 2. 计算Attention(8个头并行)
scores = (Q @ K.transpose(-2, -1)) / math.sqrt(self.head_dim)
attn_weights = F.softmax(scores, dim=-1)
output = attn_weights @ V
# 3. 合并多头并输出投影
output = output.transpose(1, 2).contiguous().view(batch, seq_len, -1)
return self.o_proj(output)python5.5 维度追踪#
输入: [batch, seq_len, 768]
→ Q、K、V: [batch, seq_len, 768]
→ 拆分+转置: [batch, 8, seq_len, 96]
→ Attention: [batch, 8, seq_len, 96]
→ 合并: [batch, seq_len, 768]
→ 输出投影: [batch, seq_len, 768]
关键不变量:num_heads × head_dim = 768plaintext5.6 为什么拆分后要拼接?#
拆分:让每个头专注不同方面
# Head 1 学会关注语法
# Head 2 学会关注实体
# ...python拼接:融合所有视角的信息
# 类比:8 个专家分别分析同一个案例
# 每个专家写一份 96 字的报告
# 最后拼成一份 768 字的综合报告python为什么不是简单平均?
- 拼接保留了所有信息(768 维)
- 平均会丢失信息(还是 96 维)
- 后续的 FFN 可以学习如何融合这些信息
六、常见混淆:Softmax vs RMSNorm#
6.1 很多人的疑问#
“Attention 里的 Softmax 和 Transformer Block 里的 RMSNorm 都是归一化,有什么区别?”
这是个常见的混淆!
6.2 核心区别#
| 特性 | Softmax(Attention 内部) | RMSNorm(Block 之间) |
|---|---|---|
| 位置 | Attention 计算内部 | Attention/FFN 之前 |
| 归一化对象 | 相似度分数(分数矩阵的每一行) | 词向量(每个向量的大小) |
| 目的 | 变成概率分布 | 稳定数值,防止梯度爆炸 |
| 输入 | 任意分数(-∞ 到 +∞) | 768 维向量 |
| 输出 | 0-1 之间,和为 1 | 归一化向量(方向不变) |
| 公式 | exp(x_i) / Σexp(x_j) | x / sqrt(mean(x²)) |
| 作用范围 | 每一行独立归一化 | 每个向量独立归一化 |
6.3 在代码中的位置#
# Transformer Block
def forward(self, x):
# ========== RMSNorm ==========
residual = x
x = self.input_norm(x) # ← RMSNorm:归一化词向量
# ========== Attention 内部 ==========
Q, K, V = self.q_proj(x), self.k_proj(x), self.v_proj(x)
# 拆分多头...
scores = Q @ K.T
weights = F.softmax(scores, dim=-1) # ← Softmax:归一化分数
output = weights @ V
# ========== 残差连接 ==========
x = residual + output
return xpython6.4 详细对比示例#
Softmax 示例:
# Attention 分数矩阵的一行
scores = torch.tensor([2.5, 1.3, 3.7, 0.8])
# Softmax 归一化
weights = F.softmax(scores, dim=-1)
print(weights)
# 输出: tensor([0.1722, 0.0518, 0.5678, 0.0082])
# 特点:
# - 所有值在 [0, 1]
# - 加起来 = 1
# - 大的更大(3.7 → 0.5678,占56.78%)pythonRMSNorm 示例:
# 一个词向量
x = torch.tensor([2.5, 1.3, 3.7, 0.8])
# RMSNorm 归一化
rms = torch.sqrt((x ** 2).mean())
x_norm = x / rms
print(x_norm)
# 输出: tensor([1.0698, 0.5563, 1.5833, 0.3424])
# 特点:
# - 值可以是任意正负数
# - RMS ≈ 1
# - 方向不变(只缩放大小)python6.5 记忆口诀#
Softmax: 归一化"分数分布" → 变成概率权重
RMSNorm: 归一化"向量大小" → 稳定训练
完全不同的归一化!
位置不同,用途不同,公式不同!plaintext七、RoPE 在 Attention 中的应用#
7.1 应用位置#
RoPE 在生成 Q、K 之后,计算 Attention 之前施加:
def forward(self, x, position_embeddings):
# 1. 生成 Q、K、V
Q = self.q_proj(x)
K = self.k_proj(x)
V = self.v_proj(x)
# 2. 拆分多头
Q = Q.view(batch, seq_len, num_heads, head_dim)
K = K.view(batch, seq_len, num_heads, head_dim)
V = V.view(batch, seq_len, num_heads, head_dim)
# 3. 转置
Q = Q.transpose(1, 2) # [batch, num_heads, seq_len, head_dim]
K = K.transpose(1, 2)
# 4. ⭐ 应用 RoPE(只对 Q、K)
cos, sin = position_embeddings
Q, K = apply_rotary_pos_emb(Q, K, cos, sin)
# 5. 计算 Attention
scores = Q @ K.transpose(-2, -1) / sqrt(head_dim)
attn = softmax(scores, dim=-1)
output = attn @ V
return outputpython7.2 为什么只旋转 Q 和 K?#
回顾前面提到的内容:
原因:
- Q 和 K 计算相似度 → 需要位置信息
- V 表示内容 → 不需要位置信息
流程:
1. scores = Q @ K.T ← 计算相似度(需要位置)
2. weights = softmax(scores)
3. output = weights @ V ← 加权求和内容(不需要位置)plaintext类比:
- Q、K 是”地图坐标” → 需要 RoPE
- V 是”宝藏内容” → 不需要 RoPE
八、动手实验#
完整的学习材料已开源,你可以自己运行验证:
# 克隆代码
git clone https://github.com/joyehuang/minimind-notes
cd minimind-notes/learning_materials
# 实验1:Q、K、V 基础原理
python attention_qkv_explained.py
# 实验2:Multi-Head Attention 实现
python multihead_attention.py
# 实验3:Softmax vs RMSNorm 对比
python softmax_vs_rmsnorm.pybash九、总结#
9.1 核心要点#
- ✅ Attention 的本质:计算词与词的相关性,融合上下文
- ✅ Q、K、V:数据库查询类比,不是玄学
- ✅ 权重矩阵:训练学习的参数,存在模型文件里
- ✅ 4 步流程:相似度 → 缩放 → Softmax → 加权求和
- ✅ Multi-Head:8 副眼镜看同一句话,融合多个视角
- ✅ Softmax ≠ RMSNorm:完全不同的归一化,位置和用途都不同
- ✅ RoPE 只用于 Q、K:相似度需要位置,内容不需要
9.2 Attention 的 4 步流程(记忆)#
1. Q @ K.T → 计算相似度
2. / √d → 缩放
3. softmax(...) → 归一化成概率
4. @ V → 加权求和plaintext9.3 Multi-Head 的维度变化(记忆)#
[batch, seq, 768]
→ 生成 Q、K、V
→ 拆分成 8 头: [batch, seq, 8, 96]
→ 转置: [batch, 8, seq, 96]
→ Attention(并行)
→ 转回: [batch, seq, 8, 96]
→ 合并: [batch, seq, 768]plaintext9.4 关键代码位置(MiniMind)#
- Attention 实现:
model/model_minimind.py:140-220 - Q、K、V 投影:
model/model_minimind.py:159-161 - RoPE 应用:
model/model_minimind.py:182 - 学习示例:
learning_materials/attention_qkv_explained.py
9.5 延伸思考#
-
GQA (Grouped Query Attention):
- MiniMind 使用 GQA(
num_key_value_heads=2) - 节省显存,加快推理
- MiniMind 使用 GQA(
-
Flash Attention:
- 优化 Attention 的计算和显存访问
- 训练速度提升 2-3 倍
-
Sparse Attention:
- 不是所有词都要关注所有词
- 长文本场景的优化
十、参考资料#
论文:
- Attention Is All You Need ↗ - Transformer 原始论文
- GQA: Training Generalized Multi-Query Transformer ↗ - GQA 论文
- FlashAttention: Fast and Memory-Efficient Exact Attention ↗
代码:
- MiniMind 源码:github.com/jingyaogong/minimind ↗
- Attention 实现:
model/model_minimind.py:140-220
系列其他文章:
本文作者:joye 发布日期:2025-12-29 最后更新:2025-12-29 系列文章:MiniMind 学习笔记(3/4)
如果觉得有帮助,欢迎:
- ⭐ Star 原项目 MiniMind ↗
- ⭐ Star 我的学习笔记 minimind-notes ↗
- 💬 留言讨论你的学习心得
- 🔗 分享给其他学习 LLM 的朋友