4. Query、Key、Value 概念详解

聊到注意力机制,有三个绕不开的概念:Query、Key、Value。说实话,我刚接触 Transformer 那会儿,就觉得这三个词太抽象了。什么“查询”、“键”、“值”,听着像数据库,又像字典查询。

但其实,它们的核心逻辑特别朴素。你想想看,我们去图书馆找书,是什么流程?你脑子里想的是“深度学习入门”这个念头——这就是 Query。你去索引柜里翻卡片,每个卡片上写着书名和位置——这就是 Key。你找到对应书架,抽出那本书——这就是 Value。

注意力机制的 QKV,干的完全是同一件事。

4.1 从数据库检索理解 QKV

我们先用最直观的类比把这三个角色说清楚。

角色 比喻 数学含义
Query (Q) 你脑子里要找的“主题” 当前 token 发出的“我想找什么”
Key (K) 图书索引卡上的“标签” 每个 token 对外展示的“我是什么”
Value (V) 书架上那本“书”的内容 每个 token 携带的“实际信息”

这里面有个关键点:Query 和 Key 是用来算相似度的,Value 才是最后被加权求和的那个。我在项目中遇到过不少人,代码里把 Q 和 K 算清楚了,但最后拿 Q 去加权 V——那就全错了。

4.2 Q、K、V 的来源与维度

在实际的 Transformer 里,Q、K、V 都来自同一份输入。假设输入序列有 n 个 token,每个 token 的向量维度是 d_model。那我们会用三个权重矩阵去投影:

输入序列 X: shape [n, d_model]

W_Q: shape [d_model, d_k]
W_K: shape [d_model, d_k]
W_V: shape [d_model, d_v]

Q = X @ W_Q  → [n, d_k]
K = X @ W_K  → [n, d_k]
V = X @ W_V  → [n, d_v]

为什么 Q 和 K 的投影维度都是 d_k?因为要算点积,维度必须相等。V 的维度 d_v 可以和 d_k 不同。不过在我实际调过的模型里,大部分场景下 d_k = d_v = d_model // num_heads,也就是每个注意力头的维度。

我的经验:刚入门时,我总记不住哪个是哪个。后来我给自己编了个口诀:“Q 去问,K 来答,V 是答案本身”。每次写代码前默念一遍,再没搞混过。

4.3 它们如何协作产生注意力

好,现在三个角色都到位了。它们怎么协作?流程分两步:

  1. 计算注意力权重: Q 和所有 K 做点积,再 Softmax 归一化。
  2. 加权聚合 V: 用上一步的权重去加权求和 V。

用公式表示就是:

Attention(Q, K, V) = softmax(Q @ K^T / sqrt(d_k)) @ V

其中:
Q @ K^T        → [n, n] 相似度矩阵
/ sqrt(d_k)    → 缩放,防止点积值过大
softmax(·)     → 按行归一化,得到权重
@ V            → 加权和,输出 [n, d_v]

我建议你把 Q @ K^T 这一步单独拿出来看。它的第 i 行第 j 列,就是第 i 个词(作为 Query)对第 j 个词(作为 Key)的匹配分数。这个矩阵,说白了就是整个注意力机制的灵魂。我调试模型时,80% 的问题都是从这里看出来的。

注意:这里有个极易踩坑的地方。如果你用了因果掩码(causal mask),一定是在 Softmax 之前把未来的位置置为负无穷,而不是在 Q@K^T 之后才处理。我曾经有个项目,把顺序弄反了,结果模型在推理时“看到”了未来的 token,训练损失怎么都降不下去。

4.4 多头注意力中的 QKV

实际用的 Transformer 很少只做一个注意力头。多头注意力(Multi-Head Attention)把 Q、K、V 切分成 h 个头,每个头独立做注意力,最后再拼起来。

假设 h = 8:
Q: [n, d_model] → 拆成 8 个头,每个头 [n, d_k]
K: [n, d_model] → 拆成 8 个头,每个头 [n, d_k]
V: [n, d_model] → 拆成 8 个头,每个头 [n, d_v]

每个头独立计算 Attention_i
输出拼回 [n, h * d_v] → 再经过 W_O 投影回 [n, d_model]

这里有一个常见的误解:很多人以为每个头要学不同的 W_Q、W_K、W_V。实际上,只有一个大的 W_Q,只是把输出切开了而已。但 PyTorch 的 nn.MultiheadAttention 内部实现确实会为每个头维护独立的权重——这是为了并行计算效率做的优化,逻辑上没有本质区别。

4.5 一张图理清 QKV 全流程

下面这张图把 QKV 从输入到注意力输出的完整路径画出来了。我把它放在手边,每次写 attention 层之前瞄一眼。

QKV 注意力机制全流程 输入序列 X [n, d_model] × W_Q [d_model→d_k] × W_K [d_model→d_k] × W_V [d_model→d_v] Query [n,d_k] Key [n,d_k] Value [n,d_v] Q @ K^T / sqrt(d_k) → 相似度矩阵 [n, n] Softmax 按行归一化 → 注意力权重 [n, n] 权重 @ V 输出 [n, d_v] Q 和 K 只用来算“权重” V 才是携带“信息”的载体

4.6 实际项目中的一个避坑案例

我之前调过一个翻译模型,BLEU 值始终上不去。排查了梯度、学习率、数据预处理,都没问题。最后我把注意力权重矩阵可视化出来一看——好家伙,Q 和 K 的内积值全集中在 0 附近,Softmax 之后几乎成了均匀分布,模型根本没学到注意力。

原因很简单:初始化时 d_k 设得特别大,但没有做缩放(/ sqrt(d_k)),点积值爆炸到 Softmax 的饱和区,梯度全消失了。所以,缩放因子从来不是一个“可选项”,它是保证训练稳定的必要条件。

核心总结:
  • Q 是“问”,K 是“索引”,V 是“内容”。
  • Q 和 K 算权重(点积 + Softmax),V 被加权求和。
  • 三个向量都来自输入投影,但维度可以不同(d_k 和 d_v)。
  • 多头注意力只是把 QKV 拆成多份并行计算。
  • 永远别忘记缩放 d_k。