-
现实与直觉(Intuition)
数学家最早都是物理学家或工程师。他们遇到了现实问题(比如:怎么解多元一次方程组?怎么描述空间里的物体旋转?)。
在这个阶段,他们靠直觉发现了一些规律:比如“把向量拉长再相加,好像能拼出空间里的任何位置”。这是一种朴素的、甚至有点粗糙的直觉。
-
公理与定义(Axioms & Definitions)—— 制定“游戏规则”
直觉很美妙,但不严谨。为了能让全世界的人都能无争议地讨论,数学家必须把直觉抽象成一套“自洽的系统”。
-
公理(Axiom): 游戏的最底层规则,是大家强制规定、无需证明、必须绝对服从的共识。
例如:我们强制规定
。不接受反驳,想玩这个游戏就得遵守。 -
定义(Definition): 给游戏里的动作起个名字,或者发明一个方便记录的符号。
例如:为了不再每次都写一长串方程,我们“定义”一种叫“矩阵”的排版格式。
-
-
定理与推论(Theorems & Corollaries)—— 玩游戏发现的“必然结果”
有了公理和定义,剩下的就是纯逻辑推导了。数学家在这套规则下,用逻辑推演出了很多甚至连发明者一开始都没想到的惊人结论。
-
定理(Theorem): 通过公理和定义,严格证明出来的、永远正确的真理。
例如:勾股定理。只要你承认了几何学的公理,勾股定理就是必然的结局。
-
直接规定,一个
然后我们定义两种运算:
-
定义:标量乘向量(缩放)
一个数字(标量)乘一个向量。数字只起一个作用——把这个向量拉长、缩短或反向。
比如:$3 \begin{bmatrix} 1 \ 2 \end{bmatrix} = \begin{bmatrix} 3 \ 6 \end{bmatrix}$。这里没有任何复杂的法则,就是把向量的每个成分放大 3 倍。
-
定义:向量加向量(合成)
把两个向量头尾相连,合成一个新向量。
比如:$\begin{bmatrix} 1 \ 0 \end{bmatrix} + \begin{bmatrix} 0 \ 1 \end{bmatrix} = \begin{bmatrix} 1 \ 1 \end{bmatrix}$。
这是整座大厦唯一的、绝对的底层逻辑。后面的所有东西,全是用这两个动作拼出来的。
现在,我们把地基里的两个动作连起来做:先缩放,再相加。
-
定义:线性组合
假设你有两个列向量
和 ,我给你两个数字(标量)$x_1$ 和 。你把 乘 (缩放),把 乘 (缩放),然后把它们加起来:$x_1\mathbf{v}_1 + x_2\mathbf{v}_2$。这个动作,被定义为“线性组合”。 整个线性代数,就是在研究“线性组合”。
现在有个问题,给你几个向量,通过线性组合能"够到"哪些点?
直觉是,在二维平面上,如果给你两个不平行的箭头,你调系数(可正可负可零),能凑出平面上任何一个点。如果两个箭头平行,你只能凑出一条直线上的点。这个直觉给出两个概念:
-
定义:线性无关
一组向量,如果谁都不能被其他几个的线性组合表示出来,就叫线性无关。(平面上"两个不平行"、空间里"三个不共面"。)
-
定义:基
在 n 维空间里,n 个线性无关的向量叫一组基。它们能凑出空间里任何一个向量,而且凑法唯一。
最自然的一组基是标准基:$\vec{e}_1=(1,0,\dots,0), \vec{e}_2=(0,1,0,\dots,0), \cdots$。
任何
显然就是 。所以坐标就是"用标准基去拼这个向量,需要多少份每个基向量"。
到这里,初学者已经有了一个清楚的图像:向量 = 箭头 = 一列数:空间 = 所有箭头:基 = 一套"度量尺"。
现在引入线性代数真正的主角。
-
直觉的引入
想象平面上画一个均匀网格(由
和 组成的那种方格)。 现在我对整个平面做一个"变形"——可以是旋转、拉伸、剪切,但不允许把网格弯曲、不允许把原点挪走。变形后,网格线还是直的、平行的,只是方向和间距可能变了。
这种"保持网格直、保持原点"的变形,就叫线性变换。
-
严谨的刻画
一个变换
是线性的,当且仅当它满足: -
(变换和加法可以交换顺序) -
(变换和拉伸可以交换顺序)
这两条不是公理,是对"保持网格"这个直觉的精确翻译。
-
-
关键观察
由于
,根据上面两条: $$ T(\vec{v})=x_1 T(\vec{e}_1)+x_2 T(\vec{e}_2)+\dots+x_n T(\vec{e}_n) $$
意思是:只要我知道 T 把每个标准基向量送到哪里,我就知道它把任何向量送到哪里。
这是整套理论的承重墙。不抽象,但严谨。
既然描述一个线性变换
这张表就叫
这是初学者最该死记的一句话。理解了它,后面所有事情都顺。
上面我已经得到,$T(\vec{v})=x_1 T(\vec{e}_1)+x_2 T(\vec{e}_2)+\dots+x_n T(\vec{e}_n)$,然后
如果我们要算
也就是说,矩阵乘向量 = 用向量的分量给矩阵的列加权,然后相加。换句话说,结果列向量是 A 里面列向量的线性组合。
这就是
现在最后一步。如果先做变换 S(矩阵 B),再做变换 T(矩阵 A),整体得到一个新的变换
C 长什么样?根据"矩阵第 j 列 = 变换把
- 先做 S,$S(\vec{e}_j)$ 就是 B 的第 j 列,记为
- 再做 T,$T(\vec{b}_j)=A\vec{b}_j$,这是用上一节的规则算的。
所以 C 的第 j 列就是 A 乘以 B 的第 j 列。
把所有列拼起来,就得到了矩阵乘法 C=AB 的规则。"行乘列"那个公式,只是把每一项展开写出来的样子。
关键体会:矩阵乘法之所以这么定义,是因为我们想让它对应"先做一个变换再做一个变换"。这也立刻解释了为什么
我们可以把上面的矩阵乘矩阵根据定义具体展开:
然后再把矩阵乘向量根据上面说的定义展开:
所以,
$$ A \cdot B = \begin{bmatrix} a_{11}b_{11} + a_{12}b_{21} + a_{13}b_{31} & a_{11}b_{12} + a_{12}b_{22} + a_{13}b_{32} \
a_{21}b_{11} + a_{22}b_{21} + a_{23}b_{31} & a_{21}b_{12} + a_{22}b_{22} + a_{23}b_{32} \end{bmatrix} $$
即:
然后引入内积定义,$a\cdot b = (a_1b_1 + a_2b_2 + a_3b_3)$。所以,
把拼好的列向量放回原处,就得到了完整的内积视角:
继续推导。我们已经知道,
$$ A \cdot B = \begin{bmatrix} a_{11}b_{11} + a_{12}b_{21} + a_{13}b_{31} & a_{11}b_{12} + a_{12}b_{22} + a_{13}b_{32} \
a_{21}b_{11} + a_{22}b_{21} + a_{23}b_{31} & a_{21}b_{12} + a_{22}b_{22} + a_{23}b_{32} \end{bmatrix} $$
做一点变换。以下变换基于行向量的线性组合定义:
$$ A \cdot B = \begin{bmatrix} a_{11} \cdot b_{r1}^T + a_{12} \cdot b_{r2}^T + a_{13} \cdot b_{r3}^T \
a_{21} \cdot b_{r1}^T + a_{22} \cdot b_{r2}^T + a_{23} \cdot b_{r3}^T \end{bmatrix} $$
所以,我们可以得到,C 的每一行,都是 B 所有行的线性组合,权重就是 A 对应那一行的元素。
-
推导总结
我们到目前为止进行的推导,承重墙是"矩阵的每一列 = 基向量的去向"。这个"列优先"不是审美选择,是因为我们规定向量写成列——而向量是主角,变换作用在向量上的写法是 Av(A 在左、v 在右、v 是列)。这个约定一旦定下来,矩阵就必须按列组织,后面所有推导也就都是列视角。
行视角是这条链全部走完,得到矩阵乘法的最终公式
之后,再回头来固定 得到的一种新的行视角解读。 但如果一开始就把约定反过来——把向量写成行,变换从右边作用,写作
——那么整套推导会镜像地走一遍,得到的就是"矩阵的每一行 = 基的去向",于是行视角变成了基本视角,列视角变成了推论。 所以更准确的说法是:列视角和行视角本身是对称的、地位平等的;但是在"向量写成列"这个约定下,列视角是基本的、行视角是推论。约定一旦选定,顺序就被锁死了。
把行视角应用到 XW 上,设
到这里还没有"批处理"概念,只是普通的行视角推论。
接下来引入解读:我们把
所以上式可以重写为:
关键一步:对于单个样本
这和前面那个式子的右边完全相同。所以:
这就是数据矩阵视角的严格表述:$XW$ 的第
-
列向量乘行向量
设
是一个 的列向量,$\vec{v}^T$ 是一个 的行向量: $$\vec{u}=\begin{pmatrix}u_1\u_2\\vdots\u_m\end{pmatrix},\quad \vec{v}^T=(v_1,v_2,\dots,v_n)$$
按矩阵乘法的尺寸规则,$(m\times 1)\cdot(1\times n)$ 是合法的,结果是一个
矩阵。这本身就是矩阵乘法的特例,不是新定义——只是因为 的列数和 的行数都恰好是 1,中间的求和号 缩成了一项。 直接套定义
,得到: $$\vec{u}\vec{v}^T=\begin{pmatrix}u_1v_1&u_1v_2&\cdots&u_1v_n\u_2v_1&u_2v_2&\cdots&u_2v_n\\vdots&\vdots&&\vdots\u_mv_1&u_mv_2&\cdots&u_mv_n\end{pmatrix}$$
每个位置
就是 。这种乘积叫外积(outer product),也叫秩 1 矩阵——之后会解释为什么叫秩 1。 关键观察:外积矩阵的结构非常特殊。看它的列——第
列是 。也就是说每一列都是 的某个倍数。看它的行——第 行是 ,每一行都是 的某个倍数。整个矩阵的"信息量"其实只有 和 这两个向量,所以叫秩 1。 到这里,我们没引入任何新东西,只是把矩阵乘法的定义在最简单的情形(
)下展开了一下。
然后,我们对矩阵乘法进行变换:
$$ A \cdot B = \begin{bmatrix} a_{11}b_{11} + a_{12}b_{21} + a_{13}b_{31} & a_{11}b_{12} + a_{12}b_{22} + a_{13}b_{32} \
a_{21}b_{11} + a_{22}b_{21} + a_{23}b_{31} & a_{21}b_{12} + a_{22}b_{22} + a_{23}b_{32} \end{bmatrix} = \
\begin{bmatrix} a_{11}b_{11} & a_{11}b_{12} \ a_{21}b_{11} & a_{21}b_{12} \end{bmatrix} +
\begin{bmatrix} a_{12}b_{21} & a_{12}b_{22} \ a_{22}b_{21} & a_{22}b_{22} \end{bmatrix} +
\begin{bmatrix} a_{13}b_{31} & a_{13}b_{32} \ a_{23}b_{31} & a_{23}b_{32} \end{bmatrix} = \ \mathbf{a}1 \mathbf{b}{r1}^T + \mathbf{a}2 \mathbf{b}{r2}^T + \mathbf{a}3 \mathbf{b}{r3}^T $$
也就是说,
矩阵乘法的定义式
| 视角 | 重组方式 | 看到什么 |
|---|---|---|
| 内积视角 | 固定 |
|
| 列视角 | 固定 |
|
| 行视角 | 固定 |
|
| 外积视角 | 固定 |
|
下面具体举例,
为了便于后续视角的展开,我们将
-
的列向量:$\mathbf{a}_1, \mathbf{a}_2, \mathbf{a}_3$ (均为 向量) -
的行向量:$\mathbf{a}{r1}^T, \mathbf{a}{r2}^T$ (均为 向量) -
的列向量:$\mathbf{b}_1, \mathbf{b}_2$ (均为 向量) -
的行向量:$\mathbf{b}{r1}^T, \mathbf{b}{r2}^T, \mathbf{b}_{r3}^T$ (均为 向量)
核心思想:逐个元素的标量计算(行
这是最传统的计算法则,着眼于微观的标量结果。结果矩阵
数学表达:
展开示例:
物理意义:衡量了
核心思想:$C$ 的列是
在此视角下,我们不再关注单个元素,而是将矩阵
数学表达:
其中:
物理意义:矩阵
核心思想:$C$ 的行是
这是列视角的对偶形式。我们将矩阵
数学表达:
其中:
物理意义:矩阵
行视角实际上有两种形式上不同、但数学完全等价的表述方式:展开形式和简洁形式。上面讲的是展开形式。
简洁形式核心思想:$C$ 的第
数学表达:
物理意义:这个形式强调"每一行独立通过
两者的关系:它们指向同一个数学对象,只是不同的写法。
实际使用时——简洁形式适合表达整体计算流程,展开形式适合分析结果的内部构成。
核心思想:多个秩为 1 的矩阵之和(列
这是最具宏观格局的视角。矩阵
数学表达:
维度验证:每一次外积的计算都是一个
物理意义:每一项外积 $\mathbf{a}i\mathbf{b}{ri}^T$ 都会生成一个秩为 1 的子矩阵。该视角深刻表明:任何复杂的矩阵乘法,本质上都是将信息分解为若干个只包含单个维度信息(Rank-1)的信号矩阵,然后进行线性叠加(Superposition)。这一原理是主成分分析(PCA)和低秩近似算法的基石。
首先我们需要了解 embedding 到底是什么。
假设我们现在有一堆 token(比如 5 万个词),它们之间有某种"语义关系"(语义相近 vs 语义相远)。Embedding 做的事就是把这 5 万个 token 嵌入(embed)到一个 512 维的连续向量空间里,要求:
- 语义相近的 token → 向量也相近(方向接近、距离小)
- 语义相远的 token → 向量也相远
换句话说,我们把"词的世界"(一个离散的、有语义关系的集合)嵌入到"向量空间"里,并要求保持语义关系。
这就是为什么这个操作叫 "embedding"——它完全符合数学上"嵌入"的原本含义:
数学嵌入 -> 词嵌入
把空间 X 映射到空间 Y -> 把词汇表映射到向量空间
保持 X 的结构 -> 保持词汇间的语义关系
结构=拓扑、距离 -> 结构=语义相似度
需要注意,Embedding 的核心是"保持结构"。"变成向量"只是手段,"保持语义关系"才是目的。你可以给每个词随便分配一个向量(这也叫 "embedding",但是个烂 embedding)。但好的 embedding 必须满足:向量空间里的几何关系,真实反映词之间的语义关系。
在真实的神经网络中,每种"语义特征"对应高维空间里的一个方向(一个向量),而不是一个坐标轴(维度)。
比如 "需要宾语" 这个特征,在 6 维空间里对应的方向可能是:[0.3,0.5,0.1,0.6,0.2,0.4]。
要判断一个 query 向量 q "是否在找宾语",不是看 q 的某一维是不是最大,而是看 q 和这个方向的点积:q⋅d需要宾语。点积大 = q 在这个方向上投影长 = 想找宾语。
同一个向量里,可以同时沿多个不同方向"有分量",每个方向代表一种语义特征。这就是为什么 512 维能塞下远超 512 种信息。
这涉及一个 "叠加假说 (Superposition Hypothesis)" 的重要理论,Anthropic 的可解释性团队发表过很著名的研究。
核心观察:在 n 维空间里,严格正交的方向只有 n 个,但"近似正交"的方向可以有指数级多个。
具体数字:
- 100 维空间:严格正交方向 100 个;近似正交(夹角 > 85°)方向约 10^{12} 个
- 512 维空间:严格正交 512 个;近似正交方向远超天文数字
只要不同特征的方向之间夹角够大(不一定 90°),模型就能用点积"大致"区分它们——即使有点干扰(叠加噪声),也是可接受的。
所以神经网络实际上在用叠加的方式,把成千上万种语义特征"塞"进 512 维空间。
先考虑 batch 里只有一个 sequence 的情况。例子是 "I love GPU programming"(4 个 token,dmodel = 6,每个 token 6 维)。
经过 embedding + positional encoding 后,我们有形状为
每一行是一个 token 的向量,有 6 个维度。一共 4 个 token。
三个权重矩阵的形状都是
先计算
具体算一下第 1 行(token "I")的第 1 个元素:
假设完整算下来:
同理计算
同理计算
注意这一步的形状:$Q, K, V$ 都是
如果把 X 看成多个 token 行向量纵向排列,把 WQ 看成多个列向量横向排列,那么实际进行的矩阵计算是:
从这个展开式里我们可以看到两个关键事实:
-
逐 token 独立
结果矩阵的第 1 行里,只有第 1 个 token 参与运算,没有其他 token。所以结果矩阵的第 i 行,只和第 i 个 token 有关。
-
WQ 的每一列是一个特征提取器
WQ 有 6 个列向量,每个列向量就是一个"特征提取器"。第 j 列接收 token 的 6 维原始特征,输出 1 个标量(即 query 向量的某 j 维)。6 个列向量并排工作,把 token 的 6 维原始特征投影成 6 维 query 向量。
WQ 的第 j 列决定了 query 向量的第
维"问什么问题"。所有 4 个 token 共享同一套"提问方式",但因为每个 token 的原始特征 不同,它们"问出的具体问题"也不同。
结果矩阵的维度最后是
我们可以再换个视角,把 X 看成多个 token 行向量纵向排列,把 WQ 看成一个整体,那么实际进行的矩阵计算是:
这种视角下,4 个 token,WQ,Q 这三者之间的关系非常清晰。每个 token 向量乘 WQ,会得到对应的 query 向量。
所以,和上个视角一样,每个 token 之间是独立的,每一行的变换不依赖其他行。每个 token 独立地生成自己的 query。而且,每个 token 都用同一个 WQ 做变换。
这解释了两件重要的事:
- 所有 token 共享同一套"提问方式"(参数共享)
- Token 之间没有相互影响(这一步完全并行)
TODO
另外,还有两个视角,列的线性组合,外积之和。暂时按下不表。
我们已经了解了具体的矩阵计算和看待矩阵计算的一些视角。那么现在有一个问题,为什么要让输入矩阵 X 和 WQ WK WV 相乘,得到 Q K V?
因为每个 token 要扮演三种不同的信息角色:Query,Key,Value。
在 token 的 embedding 向量里,携带的信息大致分成几大类,这些都可能成为 attention 匹配的依据:
- 语法信息(Syntactic):抽象、结构性、和具体内容无关。
- 词性(动词/名词/形容词)
- 句法角色(主语/谓语/宾语)
- 时态、单复数、人称、语态
- 可被什么修饰、能修饰什么
- 句法依赖关系
- 语义信息(Semantic):token 传达的意义和意思,是 embedding 最常见的研究对象
- 概念类别(动物、植物、地点、情感...)
- 情感色彩(积极/消极/中性)
- 抽象 vs 具象("love" 抽象,"apple" 具象)
- 强度("like" < "love" < "adore")
- 语义关联("king" 和 "queen" 距离近、"king" 和 "banana" 距离远)
- 世界知识(World Knowledge):token 在现实世界里的属性,百科知识
- "Paris" → "在法国"、"是首都"、"有埃菲尔铁塔"
- "Einstein" → "物理学家"、"提出相对论"、"20 世纪初"
- "GPU" → "计算硬件"、"NVIDIA"、"用于深度学习"
- 语用信息(Pragmatic):不在 token 字面意思里、需要从语境推断的,比如 could you possibly close the window 比 close the window 更礼貌
- 说话人的态度(讽刺、严肃、玩笑)
- 礼貌程度
- 正式 vs 口语
- 隐含前提
- 篇章信息(Discourse):跨句子的关系
- 指代关系("it" 指什么)
- 篇章衔接("however"、"therefore" 表达的逻辑关系)
- 主题连贯性
- 文体(学术、新闻、小说、代码...)
- 形态信息(Morphological):token 的"内部结构"
- 词根、词缀("un-+do"、"runn-+ing")
- 派生关系("happy" → "happiness")
- 屈折变化("go" / "went" / "gone")
- 位置 / 上下文信息(Positional / Contextual):位置信息严格说不属于 token 本身,但通过 PE 加进了向量里
- 在句子里的位置(开头/中间/结尾)
- 前后是什么 token
- 在篇章中的层级(标题/段落首句/正文)
- 统计 / 频率信息(Statistical)
- 这个 token 在语料中常见还是罕见
- 它经常和哪些词共现
- 它的分布模式
我们上面说过,512 维空间里可以塞下几万个近似正交的方向,所以一个 token 向量可以同时编码多种类型的信息。
含义:token i 作为信息接收方时,表达"我需要什么样的上下文信息,来让我当前位置的表示变得更完整"。
用途:用来和其他 token 的 key 做点积,衡量"我的需求和他们的供给方向匹配度有多高"。
关键澄清:
- "需要什么信息" 是从"让这个位置的向量表示更好"的角度说的,不是 token 主观想找什么
- 对一个 token 来说,它"需要什么"取决于它在句中的角色——比如宾语需要找修饰词和支配动词;代词需要找指代对象
- 一个 Query 向量同时编码多种需求(因为是高维向量),后面会详细说
含义:token i 作为信息提供方时,表达"我这个位置能提供什么样的上下文信息,能匹配什么样的需求(query)"。
用途:被其他 token 的 query 扫描并匹配,决定"我应该被谁关注"。
关键澄清:
- Key 是描述性的"可匹配标签",不是实际内容
- Key 决定搜索时能被谁找到,但不是搜索结果本身
- Key 和 Query 在同一个语义空间里进行匹配。这不是设计选择,是点积的数学性质决定的——只有在同一空间里的两个向量,点积才能有意义地衡量它们的"方向对齐度"。如果 Query 和 Key 在不同空间里(维度或语义含义完全不同),点积算出来就是一堆无意义的数字。
- 一个 Key 向量同时编码多种需求,后面详细说
含义:token i 作为信息提供方时,表达"如果我被选中了,我实际要贡献给对方的内容是什么"。
用途:当 token j 决定要关注 token i 时(即 Aji 较大),它会从 vi 中"提取内容",按权重加到自己的新表示里。
关键澄清:
- Value 是实质性的"内容载体",是被传递的具体信息
- Value 不参与"匹配"过程(Q 和 K 负责匹配),只参与"混合"过程
- Value 和 Key 在不同的语义空间里——Key 是"能被谁搜到的标签",Value 是"搜到后看到的实际内容",两者功能不同,所以用不同的投影矩阵
-
为什么需要 Q 和 K 两个不同的角色?
因为"需要什么"(Query)和"提供什么"(Key)在语言里是不对称的。Q 和 K 用两个不同的投影矩阵,可以让"需求"和"供给"可以独立编码。
-
为什么需要 K 和 V 两个不同的角色?
因为"什么样的信息能被搜到"(Key)和"搜到后得到什么内容"(Value)是两种不同性质的信息。
如果 K = V,就是强迫"匹配逻辑"和"内容"挤在同一个向量里——模型会失去一层自由度。
-
为什么三个角色要来自同一个 token 向量?
token i 需要什么(Query)、能被谁搜到(Key)、能提供什么内容(Value)——这三件事都取决于 token 本身的语义。就像一个人"想找什么对话对象""能被谁感兴趣""能分享什么内容",都源于"这个人是谁"。
所以三个角色共享同一个源头 xi,只是通过三个不同的投影矩阵拎出不同的侧面。这就是"一个向量,三副面孔"。
-
网页搜索
- Q 像用户搜索框里打的关键词
- K 像网页的 meta tag / 关键词
- V 像网页的正文内容
核心洞察:
- Q 和 K 一起决定"谁关注谁"(匹配机制)
- V 决定"关注之后得到什么"(内容机制)
- 这两层被刻意解耦——让"匹配逻辑"和"内容传递"独立优化
-
学术会议
想象一场学术会议,每个人(token)身上都挂着三张卡片:
-
Query 卡(胸前,朝向自己):写着“我今天想讨论的问题”
只有我自己能看清。为什么?因为 Query 是"我的需求",是指导我自己该去关注谁的,不需要给别人看。
比如 love 的 Query 卡写着:
"我是一个动词,表示"喜爱"这个情感动作。我需要知道:谁在执行我(找主语)?我作用的对象是什么(找宾语)?有没有修饰我的副词?我的时态信息在哪?"
比如 programming 的 Query 卡写着:
“我是一个名词,指"编程"这个活动/概念。我需要知道:我是什么类型的编程(找形容词)?我是哪个动作的对象(找动词)?我的主语是谁?”
-
Key 卡(背后,朝外):写着"我擅长的领域,我能回答的问题类型"
供别人扫描匹配。为什么?因为 Key 是"我的可匹配标签",作用是让别人来找到我。
比如 love 的 Key 卡写着:“我是一个动词。适合做:句子的主要谓语,表达情感/态度的词,连接主语和宾语的动作”
比如 programming 的 Key 卡写着:"我是个名词,适合做:动词的宾语,可以被修饰的名词,技术/计算机领域的概念,抽象活动的名称"
-
Value 卡(手里):写着"如果你来找我聊,我能给你的具体内容"
等人来了才拿出来给看。为什么?因为 Value 是"被选中后才传递的内容"——只有匹配上了的人才能"抄写"。
比如 love 的 Value 卡写着:“我代表喜爱/热爱这个语义概念:这是一种积极的情感倾向,主体对客体有正面评价和依恋,属于及物动词,语法上连接主语和宾语,情感强度比 like 强比 adore 弱。
比如 programming 的 Value 卡写着:"我表示编程这个语义概念:一种使用代码让计算机执行任务的活动,属于技术/计算机领域,是一个抽象名词,不可数,通常需要一个"类型"的修饰词来具体化"
会议开始以后,每个人(token)会做两件事:
- 匹配阶段:每个人拿着自己的 Query 卡,去看所有人(包括自己)的 Key 卡,决定"哪些人值得聊",这是 QKT
- 信息吸收阶段:根据匹配度,从所有人手里的 Value 卡里按比例"抄写内容",融合成自己的新笔记,这是 AV
-
attend_to: I love GPU prog
┌────────────────────────────────┐
I asking │ 0.60 0.15 0.10 0.15 │ ← 和=1
love asking │ 0.25 0.15 0.10 0.50 │ ← 和=1
GPU asking │ 0.10 0.10 0.30 0.50 │ ← 和=1
prog asking │ 0.10 0.20 0.55 0.15 │ ← 和=1
└────────────────────────────────┘
可以看到,I 对应的那一行,I 把注意力大部分给力自己(0.6)。因为 I 作为主语,它的语义已经比较独立(就是指代说话者自身),不太需要从上下文吸收更多信息来完善自己的表示,所以它的 Query 向量和自己的 Key 向量最匹配("自足")。
love 对应的那一行,把 0.5 的注意力给了 programming。因为 love 作为动词,它的完整含义需要知道"爱什么"——它的 Query 向量里强烈编码了"找宾语"的需求方向,这个方向和 "programming" 的 Key 向量匹配度最高(因为 programming 的 Key 向量里编码了"我是名词""我能当宾语"等信息)。
programming 对应的那一行,把 0.55 给了 GPU,0.20 给了 love。这和它的 Query 卡完全一致:
- "找修饰我的形容词" → GPU 的 Key 卡写着"可以做名词修饰词" → 高度匹配(0.55)
- "找支配我的动词" → love 的 Key 卡写着"表达情感/态度的动作" → 中度匹配(0.20)
- "找动作的发出者" → I 的 Key 卡(作为主语代词)→ 弱匹配(0.10)
programming 的 Query 在多个方向上都有分量,所以它的注意力也分散在多个 token 上,而不是像 I 那样集中。
通过上面的例子,我们可以看到,一个 Query 向量,可以同时编码多个需求(因为它是高维向量,可以在多个方向上有分量)。
programming 的 Query 向量里同时有这些方向的分量:
- "想找修饰我的形容词" → 匹配到 GPU
- "想找支配我的动词" → 匹配到 love
- "想找动作的发出者" → 微弱匹配到 I
- 可能还有其他语义方向
所有这些需求被编码在同一个向量里,通过点积在 attention 的一步中一起完成匹配。
同理,Key 和 Value 高维向量中,也编码了多层含义。
前面说 "qlove 想找宾语"、"qprogramming 想找修饰词"——这些都是我们人类事后观察到的现象,不是模型的设计目标。
真实的情况是:
- 模型通过训练学出了 WQ,WK,WV 这三个矩阵
- 这些矩阵把输入向量投影到三个新空间
- 然后按"方向匹配度"混合信息
- 整个过程的优化目标只有一个:让下一个 token 预测得更准
- 为了让预测准,模型自发地学出了"让 love 的 query 指向宾语方向""让 programming 的 query 指向修饰词方向"这种行为
这些语言学上的"意图"是模型自己学出来的副产品,而不是我们告诉它该这么做的。
所以更严谨的说法是:programming 的 Query 卡上写的是"一堆模型学出来的向量分量",而这些分量恰好在功能上等价于"找修饰词、找支配动词、找动作发出者"等语言学需求。
把 X 这个向量"旋转 + 拉伸"到一个新的空间,这个新空间的坐标方向,恰好对齐了"当下想找什么"这类语义特征。
通俗点说:
- 原始 X 向量里,"动词身份" 和 "当前想找什么" 这两种信息混在一起、方向重叠
- WQ 的作用是把"当下想找什么"这类信息"拎出来"、放到更显眼的方向上
- 同时"动词身份"这类对 Query 任务不重要的信息,会被 WQ 压缩或丢弃
总结起来:
- WQ 把"我需要什么"这类信息从 X 里拎出来 → 放到"需求空间"
- WK 把"我能被谁需要"这类信息从 X 里拎出来 → 放到"标签空间"(和 Q 同空间)
- WV 把"我能贡献什么内容"这类信息从 X 里拎出来 → 放到"内容空间"
三个矩阵本质上都在做"从 X 的混合特征里,提取对某个任务有用的信息"。区别在于它们"提取的侧重点"不同——这是通过训练学出来的。
Step 1 里我们得到了 Q, K, V 三个矩阵。现在进入 Step 2——用 Q 和 K 算出每个 token 对每个 token 的"关注度分数"。
首先计算 Q KT。把
现在做
具体算 scores 第 2 行第 4 列($q_{\text{love}} \cdot k_{\text{prog}}$),就是两个 6 维向量的点积:
假设完整结果:
k_I k_love k_GPU k_prog
┌──────────────────────────────┐
q_I │ 0.85 0.68 0.72 0.88 │
q_love │ 0.78 0.72 0.75 0.90 │
q_GPU │ 0.82 0.70 0.76 0.91 │
q_prog │ 0.93 0.75 0.82 1.02 │
└──────────────────────────────┘
scores 是
当维度 dk 很大时(比如 512),即使方向几乎随机,两个向量点积的绝对值也会因为维度多而变得很大(每一维的小贡献加起来就很大)。这会让 softmax 里的 e^qk 出现极端值,分布变得一边倒(某个 token 权重接近 1,其他接近 0),失去了"软加权混合"的意义。
除以 √dk 就是把方向匹配度标准化——让"方向接近程度"保持在一个合理的数值范围,无论维度多大。这个 scaling 不是工程上的 tweak,是数学上必要的归一化。
缩放之后,数值进入了一个更合理的范围。接下来就是 Softmax,即把这些原始分数转换成概率分布。
还是逐行操作,每行变成和为 1 的概率分布。
算第 2 行(love 的注意力分布)作为示例:
可以看到,我的示例 Q,K 数值差异太小,算出来的注意力分布几乎是均匀的(0.243~0.262),这在真实训练好的模型里几乎不会出现。为了让后续步骤的注意力模式有教学价值,我手动设定一个更典型的注意力矩阵(真实模型通过训练会学出类似的有倾向性的分布):
attend_to: I love GPU prog
┌────────────────────────────────┐
I asking │ 0.60 0.15 0.10 0.15 │ ← 和=1
love asking │ 0.25 0.15 0.10 0.50 │ ← 和=1
GPU asking │ 0.10 0.10 0.30 0.50 │ ← 和=1
prog asking │ 0.10 0.20 0.55 0.15 │ ← 和=1
└────────────────────────────────┘
这就是注意力矩阵
形状变换:$(4, 4) \times (4, 6) = (4, 6)$。注意,这次两个矩阵的 "4" 消失了(相乘相加),剩下
AV 结果的每一行 = V 里所有行向量的线性组合(加权和)。具体计算如下:
以第 2 行(love)为例,详细算一下:
代入
$$\begin{aligned} \text{out}_{\text{love}} = ; & 0.25 \cdot [0.45, 0.30, 0.40, 0.29, 0.38, 0.25] \
- ; & 0.15 \cdot [0.41, 0.25, 0.46, 0.23, 0.32, 0.44] \
- ; & 0.10 \cdot [0.31, 0.52, 0.26, 0.39, 0.48, 0.30] \
- ; & 0.50 \cdot [0.35, 0.49, 0.33, 0.47, 0.44, 0.38] \end{aligned}$$
最后算出来
对其他行做同样的计算,最终输出:
和输入
意义:$\text{out}{\text{love}} = 0.25 \cdot v{\text{I}} + 0.15 \cdot v_{\text{love}} + 0.10 \cdot v_{\text{GPU}} + 0.50 \cdot v_{\text{prog}}$ 实际上,是按 love 对 I love GPU prog 的关注程度,对 I love GPU prog 提供的语义进行了加权和,生成最后 love 对应的结果。
具体来说,love 这个位置的新表示,是它从其他 token 那里按需"抄写"value的结果:
- 它抄了 prog 50%(因为它最需要 prog 的信息——找宾语)
- 抄了自己 15%(保留一部分自己原有的信息)
- 抄了 I 25%(中等需要——找主语)
- 抄了 GPU 10%(弱需要)
最后这个新 love 向量里,装着:
- 50% 的 "编程是一种使用代码让计算机执行任务的活动" 这种语义
- 25% 的 "I 这个主语是说话者自身" 的语义
- 15% 的 "love 自己原本表示喜爱情感" 的语义
- 10% 的 "GPU 是计算机硬件" 的语义
新 love 已经不是单纯的"喜爱"了,而是"在 I 这个主语下、对 GPU programming 这个技术对象的喜爱"——一个上下文化的 love。
每一项是什么意思?我们看第 4 项(关于 prog):
这是一个
- 第 1 行:$0.15 \cdot v_{\text{prog}}$(prog 给 I 贡献的部分)
- 第 2 行:$0.50 \cdot v_{\text{prog}}$(prog 给 love 贡献的部分)⭐
- 第 3 行:$0.50 \cdot v_{\text{prog}}$(prog 给 GPU 贡献的部分)⭐
- 第 4 行:$0.15 \cdot v_{\text{prog}}$(prog 给自己贡献的部分)
这个矩阵就是"prog 这个 token 对最终 output 的全部贡献"!
同理,第 1 项是 "I 的全部贡献"、第 2 项是 "love 的全部贡献"、第 3 项是 "GPU 的全部贡献"。
最后:
现在
1. 通过输入和权重生成 Q K V
X (4, 6) ← 4 个 token,每个 6 维
│
├─ × W^Q (6, 6) ──→ Q (4, 6) ← W^Q 左边是 6,和 X 的 6 对齐
├─ × W^K (6, 6) ──→ K (4, 6)
└─ × W^V (6, 6) ──→ V (4, 6)
2. Q 和 K 生成 A
Q (4, 6) × K^T (6, 4) ──→ scores (4, 4) ← "6" 消失,剩下 token×token
│
÷ √6 ──→ scaled (4, 4)
│
softmax(每行) ──→ A (4, 4) ← 每行和=1 的概率分布
3. A 和 V 相乘,得到结果
A (4, 4) × V (4, 6) ──→ output (4, 6) ← "4" 消失,回到 token×feature
← 和输入 X 同形状!
- 就是在问:"每对 token 之间有多像?"
- 答案维度是
,和特征数无关
- 就是在说:"每个 token 的新表示 = 其他 token 的特征按权重混合"
- 答案维度回到
,和原输入一样
一句话记忆:
- 第一次矩阵乘法:特征维消失,得到关系
- 第二次矩阵乘法:token 维消失(作为权重被用掉),得到新特征
Self-attention 本质上就是两次矩阵乘法 + 一次 softmax:第一次算关系($n \times n$ 的注意力矩阵),第二次按关系混合信息(回到
Attention 的本质是,用 Q 扫描 K,按匹配度从 V 里取信息。
self-attention 的 Q K V 都来自同一个 sequence。句子里的 token 互相关注、互相吸收信息。大模型一般都是 decoder-only,只有 self-attention。
cross-attention 的 Q 来自一个序列,K V 来自另一个序列。这种用法常见于翻译模型——decoder 用目标语言的 token 作为 Q,去关注 encoder 输出的源语言信息(K 和 V)。
transformer 如何进行翻译任务 I love GPU programming -> 我爱 GPU 编程?两种 attention 都用到了。
-
Encoder 内部(self-attention):
- 处理英文输入 "I love GPU programming"
- 让英文 token 之间互相关注
- 输出一组"上下文化的英文表示"
-
Decoder 内部(self-attention):
- 处理已生成的中文部分(比如已生成 "我 爱")
- 让中文 token 之间互相关注
- 决定下一个中文词应该是什么
-
Decoder 看 Encoder(cross-attention):
- Decoder 当前位置的 Q 来自中文("爱")
- Encoder 输出的 K、V 来自英文("I love GPU programming")
- 决定生成下一个词时该关注英文的哪一部分
我们前面讲的是 single-head attention。
但是 single-head 有个结构性局限:整个序列只产生一个 4x4 的注意力矩阵。意思是:所有 token 之间的关系,只能用一种"关注模式"来表达。
但实际语言里,token 之间的关系是多样的:
- 语法关系(动词找宾语)
- 修饰关系(形容词找名词)
- 指代关系(代词找指代对象)
- 情感关系(情感词找被情感的对象)
- ……
single-head 被迫把所有这些关系挤进一个注意力矩阵里——这导致它必须"取折中",每种关系都学不够好。
multi-head 的思路,用一句话概括就是,把特征维度切成 h 份,让 h 个独立的 attention 并行运行——每个头学一种关系。最后把结果拼起来。
为了画面清爽,我们用
通过 X 和 WQ WK WV 生成 Q K V,这个操作和之前一样。但是现在我们需要对 Q K V 进行切分。
假设已经算出来:
关键操作:把 Q 沿特征维度切成两半。
前 3 维 (Head 1) 后 3 维 (Head 2)
┌────────────────┐ ┌────────────────┐
│ 0.39 0.42 0.28 │ │ 0.47 0.29 0.44 │ ← q_I
│ 0.32 0.43 0.50 │ │ 0.45 0.39 0.34 │ ← q_love
│ 0.47 0.34 0.33 │ │ 0.38 0.32 0.49 │ ← q_GPU
│ 0.51 0.46 0.43 │ │ 0.40 0.34 0.51 │ ← q_prog
└────────────────┘ └────────────────┘
Q^(1) Q^(2)
(4, 3) (4, 3)
所以:
同样地,把
Head 1 拿到
注意:每个头的注意力矩阵
Head 2 同样过一遍,用
得到 head_2 也是
注意,两个头会学到不同的关系——这是 multi-head 的精髓
因为
假设训练完后:
Head 1 的注意力矩阵(学到了"语法关系"):
—— love 找宾语→prog(0.6)、GPU 修饰 prog(0.5)、prog 找修饰词→GPU(0.5)
Head 2 的注意力矩阵(学到了"主语-动作"关系):
—— love 找主语→I(0.5),其他几行也都对 I 给了较多关注
两个头看到了不同的关系模式——这就是 multi-head 的力量。如果只有一个头,它只能在这两种模式里取折中,两种都学不好。
每个头的输出是
head_1 (4, 3) head_2 (4, 3)
┌────────────────┐ ┌────────────────┐
│ h1_I (3 d) │ │ h2_I (3 d) │
│ h1_love │ │ h2_love │
│ h1_GPU │ │ h2_GPU │
│ h1_prog │ │ h2_prog │
└────────────────┘ └────────────────┘
拼接(concatenate):
┌─────────────────┬─────────────────┐
│ h1_I │ h2_I │ ← 6 d
│ h1_love │ h2_love │
│ h1_GPU │ h2_GPU │
│ h1_prog │ h2_prog │
└─────────────────┴─────────────────┘
result (4, 6)
形状:$(4, 3) ;||; (4, 3) = (4, 6)$(沿特征维度拼接)。注意:拼接后的形状回到了
重要洞察——拼接后向量的"前 3 维"装着"语法关系信息","后 3 维"装着"主语-动作关系信息"。两种信息并排在同一个向量里,但还没有混合。
拼接结果还需要乘一个矩阵
形状:$(4, 6) \times (6, 6) = (4, 6)$
拼接后的向量里——"前 3 维"是 head_1 的产物(语法关系),"后 3 维"是 head_2 的产物(主语-动作关系)——它们还是"井水不犯河水"地并排放着。
具体说:
-
的每一列(共 6 列)都会把"前 3 维"和"后 3 维"做线性混合 - 这样输出向量的每一维都同时包含两个头的信息
没有
X (4, 6) ← 输入
│
├── × W^Q ──→ Q (4, 6) ──┐
├── × W^K ──→ K (4, 6) ──┤── 沿特征维切成 h=2 份
└── × W^V ──→ V (4, 6) ──┘
│
↓
┌──────────────────┬──────────────────┐
│ │ │
│ Head 1 │ Head 2 │
│ Q^1, K^1, V^1 │ Q^2, K^2, V^2 │
│ (4, 3) │ (4, 3) │
│ │ │
│ Attention │ Attention │
│ ↓ │ ↓ │
│ head_1 (4, 3) │ head_2 (4, 3) │
└────────┬─────────┴────────┬─────────┘
│ │
└───── Concat ─────┘
│
(4, 6) 拼接后
│
× W^O (6, 6)
│
output (4, 6) ← 和输入同形状
我们前面讲的是,先用 WQ 乘输入矩阵,然后把结果切两半。WQ 大小是 6 x 6。
论文里写的是,每个头有自己独立的矩阵
这两种实现数学上完全等价——一个 (6,6) 的大矩阵,按列切两半就是两个 (6,3) 的小矩阵。结果一模一样。实际工程实现中用大矩阵更高效(一次大矩阵乘法 vs 多次小矩阵乘法),所以代码里通常这么写。
-
Q 和 K 的差异
single head 的 Q 必须同时编码多种"需求方向"(找宾语 + 找主语 + 找时态 + ...),multi-head 的每个头的 Q 基本只专注一种或少数多种方向。
同理,single head 的 K 要混合多种"可匹配标签",multi-head 的每个头的 K 基本只装一种或少数多种标签。
-
V 的差异
multi-head 的每个头的 V 也是"针对自己任务的精选内容",不是 single head 那种"通用内容"。
-
A 矩阵的差异
single head 的 A 是"多种关系的妥协结果",multi-head 的每个 A 是"单一关系的精确刻画"。
具体来说,single head 的“局限”不是装不下多种关注模式——是不同关注模式被强迫"合并"到同一个 n×n 矩阵里互相干扰。
假设 love 想同时做两件事:强烈关注 prog(找宾语)、中等关注 I(找主语)。
Single head 的 A 矩阵的 love 那一行,会得到一个单一的注意力分布:0.3 0.1 0.1 0.5。这是"两种关系的折中表现",但这个分布本身没有标签说"0.5 是因为找宾语、0.3 是因为找主语"。下游的 V 加权时,这两种意图的内容会被"按 0.3:0.5 混在一起",信号就糊了。
Multi-head 不一样:
head A (找宾语): A_love = [0.1, 0.1, 0.1, 0.7] → 强烈混入 v_prog head B (找主语): A_love = [0.6, 0.1, 0.1, 0.2] → 强烈混入 v_I两种关系的输出是分别算出来的,然后通过 concat + W^O 整合,不会在 A 里就被折中混合。
研究者事后观察 attention 模式时有以下发现:
-
WAQ(head A 的 Q 投影)有这样的效果:
它把 X 投影到一个特定的子空间。这个子空间的特点是——
- 当 X 在"动词方向"有大分量 + "找主语方向"有大分量时 → 投影出的 q 较大
- 当 X 没这两种特征时 → 投影出的 q 较小
所以本质上,head A 的 Q 编码的是 "动词 AND 找主语" 这一组合属性,不只是"找主语"。
-
WAK 同理,它编码的是 "名词 AND 适合做主语" 的组合属性。
-
A 矩阵里数值大的位置 = "Q 同时是动词+在找主语" 和 "K 同时是名词+适合做主语" 都满足的地方——这才是为什么 A 矩阵呈现出"动词 → 主语"的模式。
换句话说,A 矩阵不是单纯的"找 XX"——是 "Q 在某种特定情境下找 XX" 和 "K 在另一种特定情境下提供 XX"匹配的程度"。那个"特定情境"——就是 token 自身的属性(是动词?是名词?时态如何?...)。
这种"主体属性也参与 attention 匹配"的现象——在可解释性研究里有个名词叫 "conditional attention pattern"——意思是 attention 模式是"有条件激活"的——只在合适的 token 类型上激活。
-
实际例子
研究者实际可视化分析过训练好的 multi-head attention 的不同头,发现它们确实在关注非常多样的信息。比如:
- 语法类
- 头 A:动词 → 它的主语
- 头 B:动词 → 它的宾语
- 头 C:形容词 → 被修饰的名词
- 头 D:介词 → 它的宾语
- 篇章类
- 头 E:代词 → 它指代的名词(指代消解)
- 头 F:每个 token → 句子的第一个 token(捕捉主题)
- 头 G:每个 token → 前一个 token(局部依赖)
- 头 H:每个 token → 句号(句子边界感)
- 语义类
- 头 I:词 → 同义词或反义词
- 头 J:实体 → 同一类别的其他实体("Paris" → 其他城市)
- 位置类
- 头 K:每个 token → 距离 -3 的 token
- 头 L:每个 token → 自己(恒等头)
- 奇怪的"功能性"头
- 头 M:所有 token → 句首的 [CLS] token(信息聚合)
- 头 N:几乎不关注任何东西("null" head,可能是冗余)
- 语法类
decoder 在做的事是自回归生成,即一个一个生成 token:
看到 "I" → 预测 "love"
看到 "I love" → 预测 "GPU"
看到 "I love GPU" → 预测 "programming"
推理时这个机制天然成立,因为未来 token 还没生成,模型想偷看也没得看。
但训练时会有问题。为了高效,训练时把整个目标序列 "I love GPU programming" 一次性喂给模型,让它并行预测所有位置的下一个 token。
如果不加 mask,当模型在 "love" 这个位置预测下一个 token(应该是 "GPU")时——它的 attention 可以看到 "GPU" 和 "programming"!那它直接 attend 到 "GPU"——把它的信息复制过来,预测准确率 100%。但这是作弊,模型没学到真本事
结果:训练时 loss 极低,推理时一塌糊涂(推理时没有未来 token 可看)。
在 attention 的 softmax 之前,把 attention 矩阵的上三角(未来位置)设成
未加 mask: 加 mask 后:
I love GPU prog
I [✓ ✓ ✓ ✓] I [ ✓ -∞ -∞ -∞ ]
love [✓ ✓ ✓ ✓] love [ ✓ ✓ -∞ -∞ ]
GPU [✓ ✓ ✓ ✓] GPU [ ✓ ✓ ✓ -∞ ]
prog [✓ ✓ ✓ ✓] prog [ ✓ ✓ ✓ ✓ ]
为什么是
加完 mask 之后,"love" 位置的 attention 只能看到 "I" 和自己,它真的需要从这里推出 "GPU"。这强迫模型学会真正的"基于上文预测"能力。
继续往下推,当我们计算 AV 的时候:
- I 的结果行向量里,只有 I 的 V 向量。即 I 对应的结果里只有 I 的语义。
- love 的结果行向量里,有 I 和 love 的 V 向量。即 love 对应的结果里有 I 和 love 的语义。
- GPU 的结果行向量里,有 I love 和 GPU 的 V 向量,即 GPU 对应的结果里有 I love GPU 的语义。
- prog 的结果行向量里,有 I love GPU 和 prog 的 V 向量,即 prog 对应的结果里有 I love GPU prog 的语义。
-
训练的正确性
如果第 i 个位置的输出会包含未来 token 的信息,那训练时就作弊了(位置 i 的预测是用了位置 i+1 的信息)。
-
训练可以并行
因为 causal mask 保证了"位置 i 只看前 i 个",每个位置的预测都是独立、合法的,可以一次性并行算所有位置的 loss。
这是 Transformer 训练比 RNN 快得多的关键原因之一。
-
KV cache
TODO