系列回顾:前两篇我们把向量和矩阵的基础打好了。这一篇聚焦在一个操作上:点积。它看起来简单,就是对应元素相乘再相加,但它是整个注意力机制的心脏。这篇文章会把点积的几何意义、数学性质、工程考量和在 DeepSeek V3 里的具体形态全部讲透,字数会比前几篇多一些,因为注意力机制值得用足够的篇幅来讲清楚。
2017 年,Google 的研究人员发表了《Attention Is All You Need》,提出了 Transformer 架构。这篇论文彻底改变了 AI 的发展轨迹,GPT、BERT、DeepSeek,几乎所有现代大模型都建立在它的基础上。
Transformer 的核心操作,说到底就是点积。
Query 向量和 Key 向量做点积,得到注意力分数;注意力分数经过 Softmax 归一化,再乘以 Value 向量,得到注意力输出。这个过程反复发生在每一层的每一个注意力头里。
为什么点积能承担如此核心的角色?它在数学上有什么特别之处?Transformer 的设计里有哪些精妙的工程考量?这篇文章逐一解答。
两个 $n$ 维向量 $\vec{a}$ 和 $\vec{b}$ 的点积:
$$\vec{a} \cdot \vec{b} = \sum_{i=1}^n a_i b_i = a_1b_1 + a_2b_2 + \cdots + a_nb_n$$
结果是一个标量,不是向量。
一个具体例子(4维):
$$\vec{a} = (1, 2, 3, 4), \quad \vec{b} = (2, 0, 1, 3)$$
$$\vec{a} \cdot \vec{b} = 1 \times 2 + 2 \times 0 + 3 \times 1 + 4 \times 3 = 2 + 0 + 3 + 12 = 17$$
上一篇提到过,点积有一个等价的几何定义:
$$\vec{a} \cdot \vec{b} = \|\vec{a}\| \cdot \|\vec{b}\| \cdot \cos\theta$$
其中 $\theta$ 是两个向量之间的夹角。
把这个公式拆开来看:
所以点积的符号完全由夹角决定:
核心直觉:点积大 → 向量方向相近 → 含义相似。点积为零 → 向量垂直 → 含义无关。点积小(负)→ 向量方向相反 → 含义对立。
这个直觉是注意力机制的基础:Query 向量和 Key 向量的点积越大,说明它们"方向越一致",即这个 Query 和这个 Key 越相关,注意力权重越高。
这几条性质在推导公式时频繁用到:
交换律:$\vec{a} \cdot \vec{b} = \vec{b} \cdot \vec{a}$(标量结果,交换没影响)
分配律:$\vec{a} \cdot (\vec{b} + \vec{c}) = \vec{a} \cdot \vec{b} + \vec{a} \cdot \vec{c}$
标量提取:$(\alpha \vec{a}) \cdot \vec{b} = \alpha (\vec{a} \cdot \vec{b})$
自点积:$\vec{a} \cdot \vec{a} = \|\vec{a}\|^2$(向量和自己的点积等于模的平方)
最后一条很有用:$\|\vec{a}\| = \sqrt{\vec{a} \cdot \vec{a}}$,计算向量长度就是自点积再开方。
注意力机制需要衡量 Query 和 Key 的相关性,点积是一种方式,但不是唯一的方式。理解各种相似度度量的差异,有助于理解为什么 Transformer 选择了点积。
把向量长度归一化后再做点积,就是余弦相似度:
$$\text{cosine}(\vec{a}, \vec{b}) = \frac{\vec{a} \cdot \vec{b}}{\|\vec{a}\| \cdot \|\vec{b}\|}$$
余弦相似度的范围固定在 $[-1, 1]$,不受向量长度影响,只衡量方向的一致程度。
优点:对向量的"大小"不敏感,只关心"方向"。在词向量里,这意味着高频词和低频词即使词向量模不同,也能公平比较语义相似度。
缺点:需要额外计算两个向量的模,计算量比裸点积多了两次开方操作和一次除法。
用向量之间的欧氏距离衡量相似度,距离越小越相似:
$$d(\vec{a}, \vec{b}) = \|\vec{a} - \vec{b}\|_2 = \sqrt{\sum_{i=1}^n (a_i - b_i)^2}$$
优点:直觉清晰,对向量的绝对位置敏感(不只是方向)。
缺点:计算量大,包含 $n$ 次减法、$n$ 次平方、$n-1$ 次加法和一次开方。更重要的是,它没法简洁地写成矩阵乘法形式,很难高效并行化。
在 Transformer 之前,Bahdanau 等人提出了另一种注意力计算方式:
$$\text{score}(\vec{q}, \vec{k}) = \vec{v}^T \tanh(W_1 \vec{q} + W_2 \vec{k})$$
把 Query 和 Key 分别做线性变换,相加后过 $\tanh$,再做一次线性变换得到标量分数。
优点:不要求 Query 和 Key 维度相同。
缺点:引入了额外的参数矩阵 $W_1, W_2, \vec{v}$,而且 $\tanh$ 操作没法像矩阵乘法一样高效并行。
Transformer 论文里明确讨论了选择点积(缩放点积注意力)的原因:
原因一:速度。点积可以写成高度优化的矩阵乘法 $QK^T$,GPU 对此有专门的硬件加速。加性注意力无法享受这个优势。
原因二:简洁。不需要额外引入可学习参数,Query 和 Key 的线性变换已经在 $W_Q, W_K$ 里学习了,点积本身是固定的操作。
原因三:有效。在实践中,缩放点积注意力和加性注意力性能相当,但速度快很多。
Transformer 用的不是裸点积,而是缩放点积注意力(Scaled Dot-Product Attention):
$$\text{score}(\vec{q}, \vec{k}) = \frac{\vec{q} \cdot \vec{k}}{\sqrt{d_k}}$$
多了一个除以 $\sqrt{d_k}$ 的操作,其中 $d_k$ 是 Query/Key 的维度。这个细节看起来微不足道,但背后有严肃的数学原因。
先做一个思想实验。
假设 $\vec{q}$ 和 $\vec{k}$ 的每个分量都是独立地从均值 0、方差 1 的分布里采样:
$$q_i \sim \mathcal{N}(0, 1), \quad k_i \sim \mathcal{N}(0, 1)$$
那么点积 $\vec{q} \cdot \vec{k} = \sum_{i=1}^{d_k} q_i k_i$ 的期望和方差是多少?
期望:
$$\mathbb{E}[\vec{q} \cdot \vec{k}] = \sum_{i=1}^{d_k} \mathbb{E}[q_i k_i] = \sum_{i=1}^{d_k} \mathbb{E}[q_i]\mathbb{E}[k_i] = \sum_{i=1}^{d_k} 0 \times 0 = 0$$
方差:
由于各分量独立,乘积 $q_i k_i$ 的方差为:
$$\text{Var}[q_i k_i] = \mathbb{E}[q_i^2 k_i^2] - (\mathbb{E}[q_i k_i])^2 = \mathbb{E}[q_i^2]\mathbb{E}[k_i^2] - 0 = 1 \times 1 = 1$$
$d_k$ 个独立项加和,总方差为:
$$\text{Var}\left[\sum_{i=1}^{d_k} q_i k_i\right] = d_k$$
所以点积的标准差是 $\sqrt{d_k}$。
问题来了:当 $d_k$ 很大(比如 128 维),点积的值会在 $\pm\sqrt{128} \approx \pm 11.3$ 的范围内波动,而不是 $\pm 1$ 的范围。
为什么点积值大了是个问题?原因在 Softmax 的行为上。
回忆 Softmax 的公式:
$$\text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}$$
当输入值很大(比如 $z = [10, 11, 12]$ vs $z = [1, 2, 3]$),Softmax 的输出会变得极度"尖锐"——最大值接近 1,其他值接近 0。
我们来算一下:
输入 $[1, 2, 3]$ 时的 Softmax:
$$e^1 \approx 2.72, \quad e^2 \approx 7.39, \quad e^3 \approx 20.09$$ $$\text{总和} \approx 30.2$$ $$\text{Softmax} \approx [0.090, 0.245, 0.665]$$
还算均匀,三个 token 都有一定权重。
输入 $[10, 20, 30]$ 时的 Softmax:
$$e^{10} \approx 22026, \quad e^{20} \approx 4.85 \times 10^8, \quad e^{30} \approx 1.07 \times 10^{13}$$ $$\text{Softmax} \approx [0.000000002, 0.0000454, 0.9999546]$$
几乎全部注意力集中在第三个 token 上,其他几乎为零。
这本身不一定是坏事,但它的梯度非常小。
Softmax 函数在输出接近 0 或 1 时,梯度趋向于零(这是 Sigmoid 族函数的通病,我们在第5篇分析过)。如果注意力矩阵里大量位置的 Softmax 输出接近 0,这些位置的梯度就几乎消失了,模型在这些位置的参数很难更新,训练变慢甚至停滞。
如果把点积除以 $\sqrt{d_k}$:
$$\frac{\vec{q} \cdot \vec{k}}{\sqrt{d_k}}$$
这个操作把点积的方差从 $d_k$ 缩小到 1:
$$\text{Var}\left[\frac{\vec{q} \cdot \vec{k}}{\sqrt{d_k}}\right] = \frac{d_k}{(\sqrt{d_k})^2} = \frac{d_k}{d_k} = 1$$
缩放后,点积的值保持在合理范围内,Softmax 不会变得极度尖锐,梯度流动正常,训练稳定。
这就是 Transformer 里除以 $\sqrt{d_k}$ 的完整数学原因。 不是随意加的一个常数,而是精确地让点积的方差归一化到 1。
现在把整个注意力机制从头推导一遍。
输入:序列的隐藏状态矩阵 $H \in \mathbb{R}^{n \times d}$,$n$ 是序列长度,$d$ 是隐藏维度。
第一步:生成 Q、K、V
$$Q = H W_Q, \quad K = H W_K, \quad V = H W_V$$
其中 $W_Q, W_K \in \mathbb{R}^{d \times d_k}$,$W_V \in \mathbb{R}^{d \times d_v}$。
$Q, K \in \mathbb{R}^{n \times d_k}$,$V \in \mathbb{R}^{n \times d_v}$。
第二步:计算注意力分数
$$S = \frac{QK^T}{\sqrt{d_k}}$$
$QK^T \in \mathbb{R}^{n \times n}$,$S_{ij}$ 是第 $i$ 个 token 的 Query 和第 $j$ 个 token 的 Key 的缩放点积。
这个矩阵也叫注意力分数矩阵,每一行表示一个 token 对所有 token 的"原始关注度"。
第三步:Softmax 归一化
$$A = \text{Softmax}(S)$$
对每一行做 Softmax,让每行的值变成非负且加和为 1 的概率分布。
$A_{ij}$ 表示:第 $i$ 个 token 在处理时,应该给第 $j$ 个 token 分配多少注意力权重。
第四步:加权求和
$$O = AV$$
$A \in \mathbb{R}^{n \times n}$,$V \in \mathbb{R}^{n \times d_v}$,结果 $O \in \mathbb{R}^{n \times d_v}$。
$O$ 的第 $i$ 行,是所有 token 的 Value 向量按注意力权重 $A_{i, \cdot}$ 做加权平均,得到第 $i$ 个 token 的输出表示。
完整公式:
$$\text{Attention}(Q, K, V) = \text{Softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
用中文句子"猫吃鱼"来说明注意力在做什么。
假设这三个词的 Query 和 Key 向量经过计算,得到注意力分数矩阵(已归一化):
这张表的含义:
每个 token 的最终表示 $O_i$,是这些权重对 Value 向量的加权平均。通过这个过程,"猫"的表示会融入"吃"的一些信息,"鱼"的表示会融入"吃"的一些信息——上下文信息在整个序列里流动了。
这就是注意力机制的本质:让每个 token 的最终表示能"借鉴"序列里其他 token 的信息,借鉴多少由点积相似度决定。
标准注意力让每个 token 能看到序列里所有其他 token,包括未来的 token。这在双向模型(如 BERT,用于理解任务)是合理的,但在语言模型(如 DeepSeek,用于生成任务)里是不对的。
语言模型要自回归地生成文字:预测第 $t$ 个词时,只能看前 $t-1$ 个词,不能看后面的词(否则等于"作弊",直接看答案)。
解决方案是在 Softmax 之前,把注意力分数矩阵的"未来位置"设置成负无穷大:
$$S_{ij} = \begin{cases} \frac{\vec{q}_i \cdot \vec{k}_j}{\sqrt{d_k}} & \text{如果 } j \leq i \\ -\infty & \text{如果 } j > i \end{cases}$$
负无穷大经过 $e^{-\infty} = 0$,Softmax 归一化后权重就变成了 0,完全屏蔽未来 token 的影响。
掩码矩阵长这样(1 表示可见,0 表示遮挡):
$$M = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 1 & 1 & 0 \\ 1 & 1 & 1 & 1 \end{pmatrix}$$
这是一个下三角矩阵——只有当前位置和之前位置是可见的,右上角全部遮挡。
DeepSeek V3 作为自回归语言模型,在每个注意力层都应用了这个因果掩码,保证生成的合法性。
单头注意力只在一个子空间里寻找关联,表达能力有限。多头注意力(Multi-Head Attention) 把特征空间拆分成多个"头",每个头独立地在自己的子空间里做注意力,最后把结果拼接在一起。
设有 $h$ 个注意力头,每个头的维度 $d_k = d / h$(把总维度平均分配):
$$\text{head}_i = \text{Attention}(QW_Q^i, KW_K^i, VW_V^i), \quad i = 1, 2, \ldots, h$$
每个头有自己独立的投影矩阵 $W_Q^i, W_K^i \in \mathbb{R}^{d \times d_k}$,$W_V^i \in \mathbb{R}^{d \times d_v}$。
把所有头的输出横向拼接:
$$\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \text{head}_2, \ldots, \text{head}_h) W_O$$
拼接后维度是 $n \times (h \times d_v) = n \times d$(如果 $d_v = d_k = d/h$),再乘以输出投影矩阵 $W_O \in \mathbb{R}^{d \times d}$,恢复到原始维度。
原因一:捕获不同类型的关联
不同的头可以学习关注序列里不同类型的关系: - 有的头可能学到"关注语法上的主谓关系" - 有的头可能学到"关注语义上的指代关系" - 有的头可能学到"关注位置上的相邻关系"
单头注意力需要用一组参数同时捕获所有这些关系,往往顾此失彼。多头则让不同头"专注于"不同方面。
原因二:更丰富的表示空间
每个头在 $d_k$ 维的子空间里做注意力,$h$ 个头覆盖了 $h$ 个不同的 $d_k$ 维子空间。这 $h$ 个子空间可以捕获输入的不同方面,拼接后的信息比单个 $d$ 维的注意力更丰富。
原因三:正则化效果
多个头的集成,比单头对噪声更鲁棒,类似于集成学习的思想。
DeepSeek V3 中,注意力头数是 128 个头,每头维度 $d_k = 128$,总维度 $128 \times 128 = 16384$。
但这里有个关键:DeepSeek V3 用的不是标准的多头注意力,而是 MLA(Multi-head Latent Attention),在多头的基础上加了低秩压缩。下面专门讲这个创新。
在推理(生成文字)时,有一个经典的优化叫做 KV Cache:把已经计算过的 Key 和 Value 矩阵缓存下来,下一个 token 生成时直接复用,不需要重新计算。
这个优化非常有效,但有一个代价:显存占用很大。
对于标准多头注意力,每个 token 需要缓存所有层的 K 和 V:
$$\text{每个 token 的 KV Cache 大小} = 2 \times d \times L \text{ 个浮点数}$$
其中 $d$ 是隐藏维度,$L$ 是层数。DeepSeek V3 有 $d = 7168$,$L = 61$ 层:
$$2 \times 7168 \times 61 \approx 875,000 \text{ 个浮点数(约 1.75 MB per token,BF16)}$$
如果要同时处理 10 万个 token(长上下文任务),KV Cache 需要 $10^5 \times 1.75 \approx 175$ GB,这是个天文数字。
MLA 的思路非常聪明:不缓存完整的 K 和 V,而是缓存一个低维的"潜在向量"(Latent Vector),推理时再从这个潜在向量还原出 K 和 V。
数学上,这是一个低秩分解(Low-Rank Decomposition)的应用:
前向传播时(训练和推理的计算):
$$c_t^{KV} = W^{DKV} h_t \quad \in \mathbb{R}^{512}$$
把隐藏状态 $h_t \in \mathbb{R}^{7168}$ 通过"下投影"矩阵 $W^{DKV} \in \mathbb{R}^{512 \times 7168}$ 压缩成 512 维的潜在向量 $c_t^{KV}$。
需要用到 K 和 V 时,再从潜在向量还原:
$$K_t = c_t^{KV} W^{UK} \quad \in \mathbb{R}^{16384}$$ $$V_t = c_t^{KV} W^{UV} \quad \in \mathbb{R}^{16384}$$
通过"上投影"矩阵还原出完整的 K 和 V。
KV Cache 里实际缓存的是 $c_t^{KV}$(512 维),而不是完整的 K 和 V(16384 维)。
节省的显存:
$$\frac{512}{16384} \approx 3.1\% \text{ 的原始大小}$$
也就是说,MLA 把 KV Cache 压缩到了原来的约 1/32,节省了 97% 的显存——这是 DeepSeek V3 能在有限硬件上高效推理的关键技术之一。
MLA 对 Query 也做了低秩压缩:
$$c_t^Q = W^{DQ} h_t \quad \in \mathbb{R}^{1536}$$
$$Q_t = c_t^Q W^{UQ} \quad \in \mathbb{R}^{16384}$$
Query 压缩的目的不是节省显存(Query 不需要缓存),而是减少计算量——先把 $h_t$ 压缩到低维,再展开,而不是直接从 7168 维映射到 16384 维,中间经过一个低维瓶颈,参数量也少了。
把 MLA 的完整流程用公式写出来,然后逐步解读:
1. KV 压缩:
$$c_t^{KV} = W^{DKV} h_t$$
2. K 和 V 解压:
$$[k_t^C; k_t^R] = W^{UK} c_t^{KV}, \quad v_t = W^{UV} c_t^{KV}$$
($k_t^C$ 是普通 Key,$k_t^R$ 是加了 RoPE 旋转位置编码的 Key,这个我们在位置编码那篇会详讲)
3. Q 压缩和解压:
$$c_t^Q = W^{DQ} h_t, \quad [q_t^C; q_t^R] = W^{UQ} c_t^Q$$
4. 注意力计算:
$$q_t^{head_i} = [q_{t,i}^C; q_{t,i}^R], \quad k_t^{head_i} = [k_{t,i}^C; k_{t,i}^R]$$
$$o_t^{head_i} = \sum_{j=1}^t \text{Softmax}\left(\frac{q_t^{head_i} \cdot k_j^{head_i}}{\sqrt{d_k}}\right) v_{j,i}$$
5. 输出投影:
$$u_t = W^O [o_t^{head_1}; o_t^{head_2}; \cdots; o_t^{head_h}]$$
每一步都是矩阵乘法加点积,这正是我们前几篇铺垫的所有内容的集中体现。
Softmax 计算时,$e^{大数}$ 会溢出。在注意力机制里,如果注意力分数很大(即使除以了 $\sqrt{d_k}$,在某些极端情况下仍然可能很大),直接算 $e^{S_{ij}}$ 会溢出。
解决方案是我们在第2篇讲过的 LogSumExp 技巧:
$$\text{Softmax}(S_i)_j = \frac{e^{S_{ij} - \max_k S_{ik}}}{\sum_k e^{S_{ik} - \max_k S_{ik}}}$$
减去每行的最大值,指数的最大输入变成 0,不会溢出,而 Softmax 的值不变。
这个技巧在所有注意力实现里都会用到,包括 DeepSeek V3 的推理框架。
标准注意力需要把完整的 $n \times n$ 注意力矩阵存到 GPU 显存里,序列越长,显存需求越大($O(n^2)$)。
Flash Attention 是一种重新组织计算顺序的方法,通过分块(tiling)计算,把注意力计算的显存需求从 $O(n^2)$ 降到 $O(n)$,同时保持计算结果完全一致。
核心思想:不把完整的注意力矩阵写入显存,而是分小块计算,每块在 GPU 的片上高速缓存(SRAM)里完成,结果直接累积到输出,避免大量慢速显存读写。
Flash Attention 用到的关键数学技巧,正是 LogSumExp:分块计算时需要维护一个"running max"和"running sum"来正确地合并各块的 Softmax 结果,这正是基于 log 的数值技巧。
DeepSeek V3 的实现使用了 Flash Attention,配合 MLA 的低秩 KV 压缩,让长上下文推理在有限硬件上成为可能。
点积不只出现在注意力机制里,深度学习的很多其他地方也用到了它。
给定一个查询词的向量 $\vec{q}$,在词表里找和它最相似的词:
$$\text{最相似的词} = \arg\max_{i} \vec{q} \cdot \vec{e}_i$$
其中 $\vec{e}_i$ 是第 $i$ 个词的嵌入向量。这是向量数据库(如 Faiss)做语义搜索的基础操作。
现代 RAG(检索增强生成)系统——DeepSeek 应用里常配合的技术——就是用点积(或余弦相似度)在大规模向量库里快速找到相关文档片段,再把检索结果喂给语言模型。
对比学习里,用点积(或余弦相似度)来训练更好的特征表示:
InfoNCE 损失函数:
$$L = -\log \frac{e^{\vec{z}_i \cdot \vec{z}_j / \tau}}{\sum_{k \neq i} e^{\vec{z}_i \cdot \vec{z}_k / \tau}}$$
注意分子和分母里都有点积,除以温度参数 $\tau$(和注意力里的 $\sqrt{d_k}$ 作用类似,控制分布的尖锐程度)。
DeepSeek R1 的训练里用到了 GRPO 算法,奖励模型的训练也有类似的对比学习思想。
用户向量 $\vec{u}$ 和物品向量 $\vec{v}$ 的点积,预测用户对物品的兴趣:
$$\hat{r}_{ui} = \vec{u} \cdot \vec{v}$$
这是矩阵分解(Matrix Factorization)推荐算法的核心——把用户-物品交互矩阵分解成两个低维矩阵的点积,学出用户和物品的嵌入表示。
虽然和 DeepSeek 没有直接关联,但这说明点积相似度是整个机器学习领域的基础工具,不只属于 NLP 或 Transformer。
看完理论,用代码把注意力机制实现一遍,把所有概念落地。
import torch import torch.nn.functional as F import math def scaled_dot_product_attention(Q, K, V, mask=None): """ 缩放点积注意力 参数: Q: Query 矩阵, shape (batch, heads, seq_len, d_k) K: Key 矩阵, shape (batch, heads, seq_len, d_k) V: Value 矩阵, shape (batch, heads, seq_len, d_v) mask: 可选的掩码矩阵, shape (seq_len, seq_len) 返回: output: shape (batch, heads, seq_len, d_v) attention_weights: shape (batch, heads, seq_len, seq_len) """ d_k = Q.size(-1) # 取最后一个维度,即 d_k # 第一步:计算缩放点积注意力分数 # Q: (batch, heads, seq, d_k), K.T: (batch, heads, d_k, seq) # scores: (batch, heads, seq, seq) scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) # 第二步:应用因果掩码(如果有) if mask is not None: # 把掩码为 0 的位置设为极大负值,Softmax 后趋近于 0 scores = scores.masked_fill(mask == 0, float('-inf')) # 第三步:Softmax 归一化(对最后一个维度,即 Key 维度) attention_weights = F.softmax(scores, dim=-1) # 第四步:加权求和 Value # attention_weights: (batch, heads, seq, seq) # V: (batch, heads, seq, d_v) # output: (batch, heads, seq, d_v) output = torch.matmul(attention_weights, V) return output, attention_weights # 验证:创建简单的测试用例 batch_size, num_heads, seq_len, d_k = 2, 4, 8, 32 d_v = 32 Q = torch.randn(batch_size, num_heads, seq_len, d_k) K = torch.randn(batch_size, num_heads, seq_len, d_k) V = torch.randn(batch_size, num_heads, seq_len, d_v) # 创建因果掩码(下三角矩阵) causal_mask = torch.tril(torch.ones(seq_len, seq_len)) output, attn_weights = scaled_dot_product_attention(Q, K, V, mask=causal_mask) print(f"输入 Q 形状: {Q.shape}") print(f"注意力权重形状: {attn_weights.shape}") print(f"输出形状: {output.shape}") print(f"注意力权重每行之和(应该全为 1): {attn_weights[0, 0].sum(dim=-1)}")
代码里每一行都对应我们讲过的数学:
torch.matmul(Q, K.transpose(-2, -1))
/ math.sqrt(d_k)
masked_fill(mask == 0, float('-inf'))
F.softmax(scores, dim=-1)
matmul(attention_weights, V)
五行核心代码,对应 Transformer 论文里最核心的公式。
这篇文章把点积从基础数学一路讲到了 DeepSeek V3 的 MLA 创新设计。
第一,点积的几何意义是衡量两个向量方向的一致程度。大的点积 → 方向相近 → 含义相似。这是注意力机制衡量 Query-Key 相关性的数学基础。
第二,Transformer 选点积而非欧氏距离或加性注意力,核心原因是效率:点积可以写成矩阵乘法 $QK^T$,GPU 有高效的硬件加速,训练和推理速度快很多。
第三,除以 $\sqrt{d_k}$ 不是随意加的常数,而是把点积方差从 $d_k$ 归一化到 1,防止高维点积过大导致 Softmax 输出极度尖锐,进而引发梯度消失问题。
第四,多头注意力把特征空间拆分成多个子空间,每个头独立学习不同类型的 token 关联,表达能力比单头更强。
第五,MLA 是 DeepSeek V3 的核心创新之一:用低秩压缩矩阵把 KV 降到 512 维再缓存,推理时解压,把 KV Cache 压缩到标准多头注意力的约 1/32,极大降低了长上下文推理的显存需求。
第六,因果掩码是自回归语言模型的必要组件,把注意力分数矩阵的上三角部分设为负无穷,保证每个 token 只能看到过去的 token。
下一篇预告:
第9篇,我们讲高维空间与矩阵分解。
MLA 里的低秩压缩(从 7168 维压到 512 维再还原)用到了一个重要的线性代数概念:低秩分解。为什么可以用低维向量来表示高维信息?什么叫"秩"?PCA 和矩阵分解有什么关系?下一篇把这些问题讲清楚,让你彻底理解 MLA 的数学本质。
点积是向量语言里的"对话"——两个向量通过点积交流彼此的相似程度。注意力机制把这种对话变成了整个序列里的信息流动,这是 Transformer 理解语言的秘密。
还没有评论,来抢沙发吧!
博客管理员
40 篇文章
还没有评论,来抢沙发吧!