QKV 的数据结构与直觉
更新于 2026-04-23
简介:为什么需要 QKV
在上一篇文章中,我们看到 Self-Attention 的核心公式:
这个公式中出现了三个矩阵:Q(Query)、K(Key)、V(Value)。它们是从哪里来的?为什么需要三个不同的矩阵,而不是直接用输入本身?
答案是:通过三组不同的线性投影,让同一个输入在不同的子空间中扮演不同的角色。这使得模型能够灵活地学习”问什么”、“用什么来匹配”和”提取什么信息”这三个本质不同的功能。
直觉理解:图书馆检索类比
想象你走进一座图书馆,想查找关于”Transformer 架构”的资料:
| 角色 | 类比 | 说明 |
|---|---|---|
| Query(查询) | 你脑中的问题 | ”我想了解 Transformer 架构” — 你带着这个问题去检索 |
| Key(键) | 书籍的标签/索引卡 | 每本书都有关键词标签:“深度学习”、“注意力机制”、“NLP”等 |
| Value(值) | 书的实际内容 | 标签匹配后,你真正读到的是书中的文字内容 |
检索过程如下:
- Query 与 Key 比较:你的问题(Query)和每本书的标签(Key)做匹配,计算”相关性分数”
- Softmax 归一化:将分数转化为概率分布 — 最相关的书获得最高权重
- 加权提取 Value:根据相关性权重,从各本书的内容(Value)中提取信息
关键点在于:标签(Key)和内容(Value)是同一本书的不同表征。标签用来快速匹配,内容才是你真正需要的信息。同理,在 Attention 中,Key 用于计算注意力权重,Value 用于聚合信息 — 它们来自同一个输入,但通过不同的投影承担不同的功能。
如果不做投影,而是直接用输入 同时充当 Q、K、V,那么模型只能计算”自身与自身的相似度”,表达能力极为有限。三组独立的投影赋予了模型更强的灵活性。
数学定义:线性投影公式和维度
输入
设输入序列经过 Embedding 后的矩阵为 :
其中:
- 是序列长度(token 数量)
- 是隐藏层维度(hidden size)
权重矩阵
三组可学习的权重矩阵:
其中 (也常写作 )是每个注意力头的维度。在标准 Transformer 中,通常 。
线性投影
Q、K、V 的计算非常简单 — 就是矩阵乘法:
每个 token 的 维隐藏向量被投影(线性变换)到 维的新空间。这就是所谓的”线性投影”(linear projection)。
为什么叫”投影”? 从几何角度看,乘以权重矩阵相当于将高维空间中的向量投射到一个低维子空间上。不同的权重矩阵定义了不同的子空间 — Query 空间、Key 空间、Value 空间。
分步可视化
下面用一个小例子(, , )演示 QKV 线性投影的完整过程:
输入矩阵 X 的形状为 (S=4, H=6)。每一行代表一个 token 的隐藏表示向量。
张量形状追踪
从输入到 Q、K、V 的每一步维度变化:
| 步骤 | 操作 | 形状 | 说明 |
|---|---|---|---|
| 1 | 输入 | 每行是一个 token 的隐藏向量 | |
| 2 | 权重矩阵 | 可学习参数 | |
| 3 | 矩阵乘法: | ||
| 4 | 权重矩阵 | 另一组可学习参数 | |
| 5 | |||
| 6 | 权重矩阵 | 第三组可学习参数 | |
| 7 |
加上 batch 维度时,完整的张量形状为:
经过投影后:
以 GPT-2 Small 为例(, 个头):
| 参数 | 值 | 说明 |
|---|---|---|
| 768 | 隐藏维度 | |
| 12 | 注意力头数 | |
| 64 | 每头维度 = |
投影前每个 token 是 768 维向量,投影后变成 64 维的 Q/K/V 向量(单个头内)。
多头场景下的 QKV
在 Multi-Head Attention 中,隐藏维度被拆分为 个头:
为什么令 ? 这不是数学上的必然要求,而是一个工程设计选择:
- 参数量守恒:合并后的权重矩阵恰好为 ,参数量与单头 Attention 的一个线性层相同,不会因为引入多头而增加参数
- 实现高效:一次矩阵乘 后,只需 reshape 即可拆出所有头。如果每个头的 不同,就无法使用 reshape,需要按不等长度切分,效率更低
- 残差连接整齐:Attention 输出需要加回输入(维度 )。每个头的输出维度为 , 个头拼接后为 。当 时,拼接维度恰好为 ,输出投影 保持方阵形式。即使拼接维度不等于 ,也可以用不同大小的 投影回去,但不如这样简洁
值得注意的是,后续的 GQA(Grouped Query Attention) 和 MQA(Multi-Query Attention) 就放松了”Q、K、V 必须有相同头数”这一假设 — MQA 让所有 Q 头共享同一组 K/V,GQA 则按组共享。不过它们的 Q 投影仍然满足 ,改变的是 K/V 的头数以减少 KV Cache 大小。
实际实现中,通常不会真的对每个头单独做矩阵乘法。而是用一个大的权重矩阵一次性投影出所有头的结果,然后 reshape:
然后将 从 reshape 为 ,再转置为 ,这样每个头就有了自己的 Query 矩阵。
投影后的矩阵 (S, H) = (4, 8),所有元素未区分 head,统一颜色。
PyTorch 引申:reshape 与 transpose 的底层机制
PyTorch 张量在底层存储三个元数据:data_ptr(内存起始地址)、shape(各维度大小)、stride(沿各维度移动一步跳过的元素数)。访问
tensor[i, j, k, l]时,实际偏移量为i×stride[0] + j×stride[1] + k×stride[2] + l×stride[3]。以 shape
(1, 4, 2, 3)为例,连续存储时 stride 为(24, 6, 3, 1):
- reshape:只修改 shape 元数据,stride 随之调整,底层内存不动,不复制数据
- transpose(1, 2):交换 dim1 和 dim2 的 size 和 stride,shape 变为
(1, 2, 4, 3),stride 变为(24, 3, 6, 1)— 同一块内存,不同的遍历顺序如何判断 non-contiguous? 连续张量的 stride 必须满足
stride[i] = stride[i+1] × size[i+1](从右到左递增)。transpose 后 stride(24, 3, 6, 1)中6 ≠ 1×3,不满足条件,PyTorch 标记为 non-contiguous。后续若调用矩阵乘法等需要连续内存的操作,需要先调用.contiguous()按新维度顺序重新排列数据到一块新内存中 — 这时才发生真正的数据拷贝。
Multi-Head Attention 的完整机制将在后续文章中详细展开。这里只需理解:QKV 投影是 Multi-Head 的基础步骤。
总结
本文介绍了 Transformer 中 Q、K、V 的核心概念:
- 直觉:Query 是”问题”,Key 是”标签”,Value 是”内容” — 类似图书馆检索
- 数学本质:三组独立的线性投影 , ,
- 维度变化:输入 经过投影变为
- 为什么需要三组:不同的投影让同一输入在不同子空间扮演不同角色,赋予模型更强的表达能力
- 多头扩展:实际中一次投影完成所有头的 QKV,再通过 reshape 拆分
下一步,我们将进入 Attention 计算详解,看看 Q、K、V 如何通过 、softmax 和加权求和完成信息聚合。