
打开任意一份现代大模型的模型结构图最显眼的组件几乎都是 Transformer。而 Transformer 之所以能取代 RNN 和 CNN 成为主流底座核心原因在于注意力机制尤其是自注意力。自注意力解决的不只是“记住之前信息”这个问题它让序列里任意两个位置可以互相直接关联既能捕捉长距离依赖又天然适合并行计算。这里不绕弯子直接围绕自注意力的数学原理、计算流程、代码实现和常见误区展开。适合正在学深度学习、准备手撕 Transformer 源码或者想搞清楚 Attention 矩阵到底在做什么的读者。1. 自注意力到底解决什么问题1.1 序列建模的老问题长距离依赖与并行在 Transformer 出现之前处理序列任务的主流方案是 RNN 和 CNN。RNN 的核心思路是逐步扫描把上一时刻的隐藏状态传给下一时刻。这样设计有一个天然问题信息必须沿着时间步一步步往后传。如果序列长度是 100那么第 100 个位置的输出要依赖前面 99 个时间步的隐藏状态传递。每一步都有信息丢失和梯度衰减距离太远时前面的信息很难真正影响后面的计算。LSTM 和 GRU 用门控机制改善了这一点但“逐步传递”的结构没有变训练和推理时依然串行无法充分利用 GPU 的并行能力。CNN 在图像领域很擅长但应用到序列时卷积核的感受野是有限的。一个 3x3 的卷积核一次只能覆盖局部区域。要建模长距离依赖只能靠堆叠很多层让感受野逐层扩大。这既增加计算量也增加调参难度。空洞卷积、分层卷积能缓解但设计起来并不直观。自注意力的思路完全不同。它不再按位置逐步传递信息而是直接计算任意两个位置之间的关联权重。序列里第 i 个 token 和第 j 个 token不管距离多远在计算注意力时都是“一步到位”。这样长距离依赖不再是瓶颈并行度也大幅提高所有位置的计算可以同时进行。这也是后来大模型普遍选择 Transformer 而不是 RNN 作为底层结构的重要原因。1.2 自注意力里的“自”指什么很多初学者第一次看到“自注意力”会以为它是一种更复杂的注意力。实际上核心差别只在“自”这个字上。在经典的 seq2seq 注意力中Query 来自解码器Key 和 Value 来自编码器。这是一种跨序列注意力模型根据当前要生成的目标词去源句子中寻找相关信息。而自注意力中Query、Key、Value 全部来自同一个输入序列。所以它能在单条序列内部建模 token 与 token 的关系不需要依赖另一个序列。用一句话概括自注意力让每个 token 都能“看到”整个序列并动态决定自己应该从哪些位置吸收信息。这个“看到”不是固定规则而是通过网络训练学出来的。为什么叫 Query、Key、Value可以把它理解成一个检索过程。Query 是当前 token 发出的查询词Key 是序列里每个 token 的索引标签Value 是每个 token 真正携带的内容。模型先比较 Query 和所有 Key 的匹配程度再按匹配权重去提取对应的 Value。这个比喻虽然不完美但对理解计算流程很有帮助。1.3 与传统注意力机制的区别传统注意力一般分两种Bahdanau 注意力也叫加性注意力用一个前馈网络计算分数Luong 注意力也叫乘法注意力用点积计算分数。Transformer 使用点积注意力并加了缩放因子。这不仅是形式上的简化还方便用矩阵运算做并行加速。自注意力和跨序列注意力在计算流程上没有本质区别都是“由 Q 和 K 算分数、softmax 归一化、再对 V 加权求和”。区别在于输入来源和用途。Transformer 编码器每一层里都是自注意力Transformer 解码器的交叉注意力层里Q 来自解码器K、V 来自编码器这是标准的跨序列注意力。理解这一点后面看源码时就不会混。2. 自注意力的完整计算过程2.1 输入和三个投影矩阵假设输入序列有 n 个 token每个 token 被表示成 d_model 维向量。可以把这个序列看成一个矩阵 X形状是 (n, d_model)。在批量训练时X 的形状通常是 (batch_size, seq_len, d_model)多了一个批次维度。自注意力的第一步是生成三个中间表示QueryQ X W_QKeyK X W_KValueV X W_V这里 W_Q、W_K、W_V 都是可学习的线性层权重。为什么需要三个不同的矩阵因为它们在概念上承担不同的角色。Q 代表“当前 token 想查找什么”K 代表“序列里每个位置能提供什么索引”V 代表“序列里每个位置实际提供的内容”。Q 是搜索词K 是图书索引V 是书的内容主体。注意力分数衡量搜索词和索引的匹配程度最终输出则是对书的内容的加权汇总。这个类比只是为了帮助理解实际上三个矩阵都是线性投影完全靠训练确定。维度方面常见设置是 d_k d_v d_model。也就是说Q、K、V 的最后一维和原始输入一致。在多注意力中则会把 d_model 分成 h 份每个头在更小的子空间里计算通常 d_k d_model / h。2.2 注意力分数公式和缩放因子注意力分数的计算是所有教材里最常见的公式Attention(Q, K, V) softmax(Q K^T / sqrt(d_k)) V分三步看。第一Q 和 K 的转置相乘得到一个形状为 (n, n) 的分数矩阵。第 i 行第 j 列表示第 i 个 token 对第 j 个 token 的原始相关程度。点积越大意味着两个向量在方向上越接近。第二除以 sqrt(d_k)。这个缩放因子是整个公式里最容易被忽略、却非常重要的细节。如果 d_k 比较大比如 64 或 128直接算出来的点积方差会很大。输入 softmax 后结果很容易变成接近 one-hot 的分布最大值的权重接近 1其他位置的权重接近 0。这样的分布会让梯度变得很小训练很难继续。除以 sqrt(d_k) 可以把点积的值域压回合理范围让 softmax 的输入分布更稳定。第三对行做 softmax得到归一化权重。2.3 softmax 归一化与加权求和softmax 的作用是把一行分数转换成概率分布保证每一行的权重之和为 1。为什么要归一化如果不归一化权重大小没有统一尺度不同样本之间无法比较梯度的稳定性也会受影响。归一化之后每个 token 对整个序列的关注程度就变成一个可解释的分布。最后一步是加权求和Output A V其中 A 是注意力权重矩阵。第 i 行的输出向量是序列中所有 token 的 V 的加权平均权重就是第 i 个 token 对其他 token 的注意力分数。因此自注意力的输出仍然是一个序列每个位置都聚合了整个序列的信息。2.4 手动走一遍小例子假设序列只有三个 tokenA、B、C。每个 token 经过投影得到 query 和 key。计算 A 的注意力分数时需要分别计算 A 和 A、A 和 B、A 和 C 的匹配度。假设三个分数分别是 2.0、1.0、0.5在 d_k 比较小的情况下softmax 后得到的结果大概是对 A 的权重约 0.63对 B 约 0.23对 C 约 0.14。接下来A 位置的新向量约等于 0.63 * V_A 0.23 * V_B 0.14 * V_C。从这个例子可以看到自注意力的“信息获取”并不是把当前 token 直接替换成另一个 token而是把所有位置的信息按权重融合起来。这也是它比简单取最大值或平均值更灵活的原因。实际数据中d_k 远大于 1数值不会像示例这么规整但计算逻辑完全相同。3. 为什么缩放因子、多头和位置编码缺一不可3.1 缩放因子稳定 softmax 的数值行为很多初学者会问既然 Q K^T 已经能表示相关程度为什么还要除以 sqrt(d_k)可以从方差角度理解。假设 Q 和 K 的每个分量都是均值为 0、方差为 1 的随机变量。两个 d_k 维向量的点积其方差会随着 d_k 增大而增大约为 d_k。如果不是除以 sqrt(d_k)当 d_k512 时点积的方差会达到 512标准差约 22.6。这么大的值经过 softmax 后绝大多数项都会被压到接近 0只有一个项接近 1。这样模型会“过度自信”梯度也会变得很小。除以 sqrt(d_k) 后点积的方差回到 1 左右softmax 的分布才会更平滑梯度也更合理。这个细节在实际训练中非常关键。尤其是使用 fp16、bf16 等低精度格式训练时数值范围本身就更敏感。如果分数动辄几百甚至上千半精度下很容易溢出出现 NaN 或 inf。所以缩放因子不是理论洁癖而是工程落地时真的要处理的问题。3.2 多头注意力从单种关联到多种关联单头自注意力只能得到一种固定模式的关联权重。但真实语言中词和词之间的关系是多层次的。比如“猫坐在垫子上”“猫”和“垫子”之间有位置关系和“坐在”之间有动作关系和“它”之间可能有指代关系。单头注意力需要在一次 softmax 里同时编码这么多关系表达力有限。多头注意力的做法是把 Q、K、V 分别投影到 h 个低维子空间每个头独立计算注意力。通常取 h8d_k d_v d_model / h。每个头学到的是不同维度的关注模式。计算结束后把 h 个头的输出拼接起来再过一层线性映射。需要注意的是多头注意力并不是把输入 X 复制成 h 份再分别计算。它是对同一个 X 做不同的线性投影每个头相当于在一组不同的特征子空间里做注意力。这样既让参数量保持可控又获得多组表达能力。Transformer 论文里提到不同头会学习到不同的语法、语义关系后来的可视化研究也验证了这一点。3.3 位置编码补偿自注意力的排列不变性自注意力有一个非常重要的特性它是排列不变的。把输入序列里两个 token 的位置互换只要它们的向量不变输出结果也不会变。对序列建模来说这是个问题。因为语言中的顺序是有意义的“小明打小王”和“小王打小明”含义完全不同。所以 Transformer 必须在输入端把位置信息编码进去。原版 Transformer 使用正弦余弦函数生成固定位置编码PE(pos, 2i) sin(pos / 10000^(2i/d_model))PE(pos, 2i1) cos(pos / 10000^(2i/d_model))这样的好处是不同位置的编码向量不同而且模型可以学习到相对位置的一些规律。后来的工作里常见做法还有可学习位置编码、相对位置编码、旋转位置编码 RoPE 等。自注意力本身不包含顺序信息这个特性决定了位置编码不是可选配置而是 Transformer 的核心组件之一。3.4 残差连接与层归一化让深度网络真正可训练自注意力通常作为