深入拆解 Transformer:从注意力到完整架构

哪怕你以为自己懂了 Transformer,把它的每一层拆开重看一遍,也总能发现之前糊弄过去的地方。

这句话我每次重读那篇 2017 年的论文都会想起。Transformer 已经成了大模型的「底层操作系统」,BERT、GPT、Llama、甚至视觉里的 ViT,本质都是它的变体。但很多人对它的理解停留在「一个用注意力替代 RNN 的模型」,问到细节——为什么要除以 $\sqrt{d_k}$、多头到底多了什么、位置编码为什么用正弦、Decoder 凭什么不能偷看未来——就答不上来。

这篇文章就只做一件事:把 Transformer 从里到外拆一遍。先讲注意力这个心脏,再装上多头、位置编码、掩码这些零件,最后拼成完整的 Encoder-Decoder。公式不会躲,但每个公式都会回答三个问题:输入是什么形状、这一步做了什么、为什么非做不可

读这篇文章只需要知道向量和矩阵乘法。为了避免后面被符号绊住,先约定:

符号含义常见形状(暂时省略 batch 维)
$n$序列中的 token 数标量
$d_{model}$每个 token 的表示维度标量
$X$一整段输入的表示$n \times d_{model}$
$d_k,d_v$单个注意力头中 Key/Value 的维度标量
$h$注意力头数标量
$d_{ff}$FFN 中间层维度标量

真实代码通常还会有 batch 维,因此 $X$ 常写成 [batch, seq_len, d_model]。本文先盯住一条序列;理解后,把同样的计算复制到 batch 中每个样本即可。

先在脑中装一张地图

Transformer 一层里其实有两种分工非常明确的计算:

  • 注意力在 token 之间搬运信息:让「它」取回「动物」的信息,让目标词去源句里找对应内容。
  • FFN 在每个 token 内部加工信息:拿到上下文以后,再独立做非线性变换。

残差连接和 LayerNorm 则像脚手架,保证很多层叠起来仍然能训练。位置编码补上顺序,Mask 规定哪些信息可以被看见。后面所有细节,都可以放回这张地图里定位。

一、它到底解决了什么问题

在 Transformer 之前,处理序列主要靠 RNN(以及 LSTM、GRU)。RNN 有两个绕不开的硬伤:

一是必须串行。 RNN 处理一句话是从左到右一个词一个词喂的,第 t 步的计算依赖第 t-1 步的隐状态。这意味着一句话再长,GPU 的并行算力也用不上,训练慢得让人绝望。

二是长距离会忘。 信息每经过一步就乘一次权重、过一次非线性,梯度像传话游戏一样层层衰减。句子开头的内容,传到结尾往往已经面目模糊。LSTM 用门控缓解了一些,但治标不治本。

CNN 倒是可以并行,但卷积每次只看局部,想让序列两端的词「看见」彼此,得堆很多层,靠感受野一点点扩大——距离越远,路径越长。

Transformer 的破局点很直接:别一步步传了,让序列里的每个位置一次性看到所有其他位置。 这就是注意力机制。两个相隔 1000 个词的 token,在注意力里只需要一次矩阵乘法就能直接交互,距离在这个机制面前被抹平了。

代价当然有——后面会讲它 $O(n^2)$ 的复杂度。但它换来的是真正的并行和全局视野,这笔交易彻底改变了深度学习的走向。

二、自注意力:Transformer 的心脏

2.1 用「检索」的思路理解 Q/K/V

注意力里最让人犯晕的就是 Query、Key、Value 这三个东西。其实用检索系统一类比就通了:

  • Query(查询):当前这个词「想找什么」
  • Key(键):每个词「能提供什么线索」
  • Value(值):每个词「真正携带的内容」

想象你在图书馆找书。你心里有个需求(Query),每本书的书名和标签是 Key,书的具体内容是 Value。你拿需求和每本书的 Key 比对,越匹配的书你越仔细看(权重越高),最后把所有书的内容按匹配度加权汇总——这就是注意力干的事。

在自注意力里,Query、Key、Value 都来自同一个输入序列,只是各自经过一个不同的线性变换($W_Q, W_K, W_V$)。换句话说,同一个句子,换三副「眼镜」看出三种角色。

2.2 计算流程:四步走

把上面的直觉写成计算,就是下面这张图:

自注意力的计算流程:X 经三个投影得到 Q/K/V,打分缩放 softmax 后加权求和

对应到一个公式,就是论文里那句被引用了无数次的:

$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V $$

拆开看是四步:

  1. 打分 $QK^T$:用每个 Query 和所有 Key 做点积,得到一个 $n \times n$ 的分数矩阵。点积越大,说明这两个词越「对口」。
  2. 缩放 $\div \sqrt{d_k}$:分数除以 Key 维度的平方根。这一步看着不起眼,作用关键,下面单独讲。
  3. 归一化 softmax:把每一行的分数转成概率分布(加起来等于 1),这就是「注意力权重」——当前词把多少注意力分给其他每个词。
  4. 加权求和 $\times V$:用这些权重对 Value 加权平均,得到每个词的新表示。这个新表示不再是孤立的词向量,而是「融合了整句话上下文」的向量。

这就是自注意力的全部。没有递归,没有卷积,全程都是矩阵乘法,所以可以充分利用 GPU 并行。

2.3 别急着背公式,先把形状走通

假设一句话被切成 4 个 token,每个 token 用 8 维向量表示,那么输入 $X$ 的形状是 $4\times8$。在一个头里取 $d_k=d_v=2$:

$$ Q=XW_Q,\quad K=XW_K,\quad V=XW_V $$

其中 $W_Q,W_K,W_V$ 的形状都是 $8\times2$,于是 $Q,K,V$ 都是 $4\times2$。接下来:

计算形状变化含义
$QK^T$$(4\times2)(2\times4)\to4\times4$4 个 Query 分别给 4 个 Key 打分
softmax$4\times4\to4\times4$每一行变成和为 1 的权重
$AV$$(4\times4)(4\times2)\to4\times2$每个位置按权重汇总 4 个 Value

这里最重要的是 $4\times4$ 的注意力矩阵:行表示「谁在查询」,列表示「它查询谁」。第 3 行第 1 列很大,意思是第 3 个 token 更新自己时,大量取用了第 1 个 token 的 Value。千万别把行列含义读反。

为什么已经有输入 $X$,还要投影出三份?因为「用什么标准匹配」和「匹配后取回什么内容」未必是一回事。数据库索引用标题检索,返回的却可以是整篇正文;$K$ 类似索引,$V$ 类似被取回的内容。$W_Q,W_K,W_V$ 都是训练得到的参数,模型会自己学会什么特征适合匹配、什么特征值得传递。

2.4 一个可以手算的迷你注意力

为了只看核心,假设某个头已经算出:

$$ q=[1,0],\quad k_1=[1,0],\quad k_2=[0,1],\quad v_1=[10,0],\quad v_2=[0,6] $$

$q$ 与两个 Key 的点积是 [1, 0]。先忽略缩放,softmax 后大约是 [0.731, 0.269],所以输出为:

$$ 0.731[10,0]+0.269[0,6]=[7.31,1.61] $$

输出并不是把某个词原样复制过来,而是 Value 的加权混合。如果 Query 更接近 $k_2$,第二个 Value 的占比就会上升。训练就是不断调整投影矩阵,让任务需要的匹配得到高分。

这个例子也揭示了一个容易忽略的事实:注意力权重只决定「从各位置取多少」,真正被搬运的是 $V$。因此只盯着热力图,不能完整解释模型做了什么;相同权重配上不同 Value,结果可以完全不同。

2.5 那个 $\sqrt{d_k}$ 为什么不能省

公式里最容易被跳过的就是缩放因子。论文给的理由很简洁:当 $d_k$ 较大时,点积的数值会变得很大,把 softmax 推进「饱和区」。

直觉是这样的:点积是 $d_k$ 个分量的和,假设每个分量均值 0、方差 1,那点积的方差就是 $d_k$,标准差是 $\sqrt{d_k}$。$d_k$ 一大(比如 64、128、512),点积值动辄几十,softmax 一看输入差距这么大,会输出一个接近 one-hot 的分布——某个位置权重几乎为 1,其余几乎为 0。

后果是梯度消失。softmax 的梯度量级是 $p(1-p)$,当 $p$ 趋近 0 或 1 时,梯度趋近 0。模型刚开始训练时参数随机,注意力本应是「广泛撒网」,结果被大数值逼成了「孤注一掷」,梯度又传不动,训练直接卡死。下图把这两件事放在一起看就很清楚:

缩放因子作用:未缩放 logit 摊得很开,多落在 softmax 梯度趋零的饱和区

左边是 $d_k=512$ 时点积的模拟分布——假设 Q/K 各分量独立、均值为 0、方差为 1,不缩放时标准差接近 22;除以 $\sqrt{512}\approx 22.6$ 后,标准差被压回 1 附近。右边用两类 softmax 等价的 sigmoid 切片展示饱和趋势:logit 差距越大,分布越接近 0/1,相应梯度也越小。多分类 softmax 的雅可比矩阵更复杂,但结论相同。缩放就是把数值拽回梯度更健康的区间。

一句话:缩放因子是给 softmax 降温的,让注意力在训练初期保持「适度分散」,梯度才能正常流动。

2.6 一个注意力矩阵长什么样

光看公式还是抽象。来看一个经典的例子——句子「The animal didn’t cross the street because it was too tired」,其中代词「it」指代谁?人一眼就知道是 animal,但模型得自己学。注意力矩阵的每一行代表一个词「在看」哪些词:

自注意力矩阵示意:「it」强烈关注「animal」,体现了指代消解

在这张教学示意图里,第 8 行「it」强烈集中在第 2 列「animal」上,它表达的是一种可能学到的指代模式:更新 it 的表示时,从 animal 取回更多信息。

实际训练出来的注意力图往往更复杂、更嘈杂,也不保证每个头都有清晰的人类语义。有些头偏向相邻位置,有些偏向标点或句首,还有不少头彼此冗余。注意力图适合观察信息路由,但**「权重高」不等于严格的因果解释**,更不能仅凭一张图断言模型完成了指代消解。

三、多头注意力:同时从多个角度看

3.1 一个头为什么不够

单头注意力只能学出一种关注模式。但语言里的关系是多层次的:一个词可能既要管「相邻搭配」(动宾结构),又要管「长距离依赖」(主谓一致),还要管「语义相似」。指望一个注意力头同时把这些都算清楚,有点强人所难。

3.2 多头的做法:切分子空间

多头注意力的思路很朴素:让 $h$ 组独立的投影参数各自算一次注意力,最后拼接起来再过一次线性变换。工程实现常把一次大矩阵投影的结果 reshape 成多个头,看起来像「切分」,但概念上不是把原始 embedding 生硬切成几段;每个头都有自己学出来的 Q/K/V 子空间

比如 $d_{model}=512$、$h=8$,就是 8 个头,每个头在 64 维子空间里算自己的 $QK^T$。计算量和单头基本相当(总维度没变),但每个头可以在自己的子空间里学不同的关注模式。

写完整一点,多头注意力是:

$$ head_i=Attention(XW_i^Q,XW_i^K,XW_i^V) $$$$ MultiHead(X)=Concat(head_1,\ldots,head_h)W_O $$

若每个头输出 $n\times64$,8 个头拼接后又回到 $n\times512$,再乘 $W_O$ 混合各头的信息。这个输出投影很重要:没有它,各头只是并排放着,彼此没有机会重新组合。

3.3 不同的头真的在看不同的东西

这可不是理论上的猜测,可视化出来一目了然:

多头注意力:不同头学到不同的关注模式

  • 有的头注意力集中在主对角线附近,关注相邻词——像 n-gram,捕捉短语结构
  • 有的头把注意力均匀铺开——在做整句的语义聚合
  • 有的头学会了对齐长距离的成分,比如把「自然语言」和「人工智能」这两个短语对应起来

所以多头的本质是给模型多组独立的匹配与取值通道。把它叫作语法镜头、语义镜头有助于建立直觉,但不要理解成设计者提前指定了每个头的职责;这些分工来自训练,也可能不够整齐,甚至存在冗余。

四、位置编码:补回被丢掉的顺序

在谈位置前,还缺输入端的一小段。原始文字不能直接进入 Transformer。Tokenizer 会把文字变成 token,再把 token 映射为整数 id。例如一个词可能是一个 token,也可能被拆成多个子词;token 不等同于汉字或英语单词。Embedding 层本质是一张可训练的查找表,把每个 id 变成 $d_{model}$ 维向量。到这一步,模型知道「是什么 token」,还不知道「它排第几个」,位置编码就是补这块信息。

4.1 注意力是个「没记性」的东西

这里有个反直觉的点:自注意力本身对顺序完全无感。

如果不加入位置信息,把输入行做同样的置换,自注意力输出也只会跟着置换。这叫置换等变性:机制知道有哪些 token,也能比较它们,却没有原生的先后概念。原因很简单——公式里只有内容投影、矩阵乘法和加权求和,没有「第几个位置」的信息。

对翻译这种顺序至关重要的任务,这是致命的。所以必须额外把位置信息「注射」回输入里。

4.2 用正弦和余弦:一个巧妙的设计

论文没有让位置信息可学习(后来的 BERT、GPT 用了可学习的位置嵌入),而是直接用一组固定的正弦/余弦函数:

$$ PE_{(pos,\,2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$

$$ PE_{(pos,\,2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$

$pos$ 是位置,$i$ 是维度编号。关键在分母里的 $10000^{2i/d_{model}}$:不同维度 $i$ 对应不同的频率,从高频到低频铺开。画成热力图就能直观看到这种设计:

正弦位置编码矩阵:低维高频、高维低频,形成多周期波形

左边(低维)是高频波,位置稍有变化值就大幅改变,编码「绝对位置」的细节;右边(高维)是低频波,位置变化时值缓慢变化,编码「长程相对关系」。整张图就像一组频率递减的波浪。

4.3 为什么不用简单的编号

直接把位置编号 1,2,3,… 加上去不行吗?不行——数值会越来越大,和 embedding 的量级对不上,模型会被大数值带偏。

为什么用 sin/cos 而不是单一函数?有两个巧处。其一是可以外推:这组函数对任意位置都有定义,理论上能处理比训练时更长的序列(实际外推能力有限,后来才有 RoPE、ALiBi 这些专门改进)。其二是相对位置可线性表达:对于固定的偏移 $\delta$,$PE_{pos+\delta}$ 可以表示成 $PE_{pos}$ 的线性函数,这意味着模型有能力通过线性组合捕捉「相对位置」——而语言里很多关系(「后面第 3 个词」)恰恰是相对的。

把位置编码和 token embedding 逐元素相加,就得到了带顺序信息的输入表示,送进后面的网络。

五、Encoder 和 Decoder:把零件装起来

有了注意力、多头、位置编码这些零件,就可以拼装完整的 Transformer 了。原版是个 Encoder-Decoder 结构,用于机器翻译:

Transformer 完整架构:Encoder(左)与 Decoder(右),编码端输出作为 K,V 注入解码端

左半边是 Encoder,右半边是 Decoder,各有 $N$ 层(论文里 $N=6$)。Encoder 负责把输入句子编码成一组上下文丰富的表示,Decoder 负责逐个生成输出词。两者通过交叉注意力连起来。

5.1 Encoder:自注意力 + FFN,配残差和归一化

一个 Encoder block 做两件事:

  1. 多头自注意力:让输入序列内部充分交互
  2. 前馈网络(FFN):对每个位置独立做一次「升维→非线性→降维」

原论文采用 Post-Norm,每个子层外面都包着一层 Add & Norm:

$$ \text{output} = \text{LayerNorm}\bigl(x + \text{Sublayer}(x)\bigr) $$

残差连接让信息能绕过子层直接流动(梯度也有了快车道),LayerNorm 稳定每层的数值分布。Encoder 没有 mask,每个词都能看到整句话,这是双向的。

这里的「没有 mask」更准确地说是没有因果 mask。如果 batch 中句子为了对齐而补了 padding,Encoder 仍会使用 padding mask,避免真实 token 关注补位符号。

5.2 Decoder:多了一层交叉注意力,还得戴眼罩

Decoder block 比 Encoder 多一个子层,结构是三层:

  1. 带掩码的自注意力(Masked Self-Attention)
  2. 交叉注意力(Cross-Attention)
  3. 前馈网络

后两个和 Encoder 同理,关键是第一个——为什么要掩码?

5.3 因果掩码:凭什么不能偷看未来

Decoder 是用来生成的,生成第 t 个词时,它只能看到已经生成的前 t 个词,绝不能偷看第 t+1 个词——否则就是作弊,训练时模型会直接抄答案,推理时(没有答案可抄)就彻底垮掉。

但自注意力天然会让所有位置互相看到。怎么办?用一个下三角掩码,把注意力矩阵的上三角(未来位置)屏蔽掉:

因果掩码:下三角可见、上三角屏蔽,保证生成时只看历史

实现上就是把上三角位置的注意力分数设成负无穷,softmax 之后这些位置的权重归零。这个掩码让 Decoder 在训练时也能像「一次喂入整个序列」那样并行计算(配合 teacher forcing,把目标序列整体右移一位作为输入),但每个位置的信息流被严格限制在「过去」。

「右移一位」最好用一个具体例子看。目标句是「我 爱 你」,训练数据会整理成:

Decoder 输入:<BOS> 我   爱
监督答案:     我     爱  你

三个位置可以一次送入 GPU,但因果 mask 保证 <BOS> 的位置看不到「我、爱」,「我」的位置看不到「爱」。模型每个位置都在预测紧随其后的 token,损失通常是预测分布与真值之间的交叉熵。Teacher forcing 指输入用真值前缀,而不是用模型自己刚预测错的词;它和因果 mask 解决的是两件不同的事。

顺便提一句,还有一类 padding mask:同一批里长短不一的句子要补齐,补的那几个位置没意义,得在注意力里一并屏蔽。Decoder 里通常把 causal mask 和 padding mask 组合起来用。

5.4 交叉注意力:编码端的信息怎么流过来

Decoder 第二层的 Cross-Attention 是连接 Encoder 和 Decoder 的桥梁。它的 Query 来自 Decoder 上一层,而 Key 和 Value 都来自 Encoder 的输出

这意味着:生成每个目标词时,Decoder 都会拿自己当前的表示去「查询」Encoder 编码出的整句源语言表示,决定该把源句的哪部分信息搬过来。翻译「I love you」→「我爱你」时,生成「爱」这个词,Decoder 就会重点去 Encoder 那边找「love」对应的表示。这一层让翻译成为可能——它把源端的语义对齐到了目标端。

如果你只关心 BERT(纯 Encoder)或 GPT(纯 Decoder),可以暂时忽略交叉注意力。但理解了它,才算理解了完整的原版 Transformer。

把三种注意力放在一起,就不容易混了:

类型Query 来自哪里Key/Value 来自哪里可见范围
Encoder 自注意力Encoder 当前表示同一份表示整个源序列(除 padding)
Decoder 因果自注意力Decoder 当前表示同一份表示当前及之前的位置
Encoder-Decoder 交叉注意力Decoder 表示Encoder 最终输出整个源序列(除 padding)

自注意力的「自」指 Q/K/V 来源相同,交叉注意力则是 Q 与 K/V 跨了两条数据流。这个判断比死记三张结构图更管用。

六、三个常被忽略的配角

注意力抢了所有风头,但有几个配角少了它们模型就跑不稳。

6.1 前馈网络:逐位置的「二次加工」

FFN 看着不起眼,结构也简单——两层全连接中间夹一个非线性:

$$ \text{FFN}(x) = \max(0,\, xW_1 + b_1)\,W_2 + b_2 $$

原版中间层维度是 $d_{model}$ 的 4 倍(512 → 2048 → 512)。注意它对每个位置独立施加,同一层 FFN 的参数在所有位置共享,不同 token 在这一步不交互。可以理解为:注意力负责「收集情报」,FFN 负责「消化情报」。后续模型常用 GELU、SwiGLU 等替代 ReLU,也不都严格维持 4 倍宽度。

这部分看似朴素,参数量却常大于注意力。忽略 bias,一层 FFN 约有 $2d_{model}d_{ff}$ 个参数;当 $d_{ff}=4d_{model}$ 时就是 $8d_{model}^2$。多头注意力四个主要投影矩阵合计约 $4d_{model}^2$。Transformer 绝不是「参数都花在 Attention 上」。

6.2 残差连接:梯度的高速公路

每个子层都套了残差,输入直接加到输出上。深层网络最怕梯度消失,残差给梯度提供了一条「跳过非线性」的直通路,让几十层的 Transformer 也能训得动。这个思路 ResNet 已经验证过,Transformer 直接继承。

6.3 LayerNorm:为什么不用 BatchNorm

归一化层用 LayerNorm 而不是图像里常见的 BatchNorm。区别在统计量的计算方向:

  • BatchNorm 沿 batch 方向统计,依赖同一批里多个样本。序列长度一变,统计量就抖,训练和推理行为还容易不一致。
  • LayerNorm 沿特征维度统计,每个样本自己归一化自己,和 batch 大小、序列长度都无关。

对变长序列来说,LayerNorm 明显更稳。原论文使用 Post-Norm:

$$x' = LN(x + Sublayer(x))$$

很多深层模型改用 Pre-Norm:

$$x' = x + Sublayer(LN(x))$$

Pre-Norm 给残差主干保留了更直接的梯度路径,通常更容易把网络堆深;Post-Norm 在一些设置下最终效果可能更好,却对初始化和训练技巧更敏感。看到不同架构图里 LayerNorm 位置不一样,不必怀疑自己记错了,它们很可能采用了不同变体。

七、完整数据流:一句话从输入到输出

把所有零件串起来,走一遍翻译的完整流程:

  1. 分词:把句子切成 token id 序列
  2. Embedding:每个 id 查表得到一个 $d_{model}$ 维向量
  3. 加位置编码:逐元素加上 PE
  4. 过 N 个 Encoder 层:自注意力 + FFN,每层都带残差和 LayerNorm,得到源句的上下文表示
  5. Decoder 接力:输入是已生成的目标序列(训练时用右移一位的真值),过 N 个 Decoder 层——masked 自注意力、对 Encoder 输出的交叉注意力、FFN
  6. 线性层 + Softmax:把 Decoder 输出映到词表大小的概率分布
  7. 采样/取最大:选出下一个词

训练时用 teacher forcing——每一步都把真值作为下一步输入,整个目标序列可以并行算出,效率极高。推理时则是 自回归——生成一个词,拼回输入,再生成下一个,逐步往前走。这也解释了为什么生成比理解慢得多:理解(Encoder)能一次并行处理整句,生成(Decoder)必须一个一个来。

7.1 训练并不是让注意力图「长得好看」

模型通常只收到最终任务的监督。以翻译为例,每个位置输出词表上的 logits,经 softmax 变成下一个 token 的概率,再与正确 token 算交叉熵。误差通过输出层、Decoder、交叉注意力一路反向传播到 Encoder 和 Embedding。没有人逐格告诉某个头该关注谁,Q/K/V 投影、FFN、Embedding 都是在降低同一个预测损失的过程中共同学出来的。

这也解释了为什么注意力头未必对应人类能命名的语法关系:模型只需要把答案预测对,并没有义务产生一张适合人类阅读的分析图。

7.2 推理为什么需要 KV Cache

自回归生成第 100 个 token 时,前 99 个 token 的 Key 和 Value 已经算过,而且不会改变。如果每一步都从头重算,浪费会越来越大。实际大模型推理会缓存各层历史 token 的 K/V,新一步只计算新 token 的 Q/K/V,再让新 Query 查询缓存中的全部 Key,并汇总对应 Value,这就是 KV Cache

它减少了重复计算,却占用随序列长度增长的显存。缓存也没有消灭注意力成本:当前 Query 仍要与全部历史 Key 匹配,因此上下文变长后,单步生成仍会越来越贵。训练阶段所有位置可以并行,一般不靠 KV Cache 获得这种收益。

八、Transformer 的代价

讲了一路好处,得说说代价,不然就成布道文了。

注意力是 $O(n^2)$ 的。 $n$ 个 token 两两算注意力,序列长度翻倍,计算量和显存都翻四倍。这就是为什么早期上下文长度卡在 512、2048,长文档成本极高。后来的 Flash Attention(算法层面减少显存读写)、稀疏注意力、滑动窗口注意力,都是在跟这个 $n^2$ 较劲。

更精确地说,一层注意力大致包含两类成本:Q/K/V 与输出投影约为 $O(nd_{model}^2)$,注意力打分与加权汇总约为 $O(n^2d_{model})$。序列较短、模型很宽时,线性投影未必比注意力矩阵便宜;当 $n$ 足够长,二次项才会成为主要矛盾。FlashAttention 并没有把精确注意力的理论计算复杂度改成线性,它主要通过分块计算、减少显存读写,避免把完整注意力矩阵反复搬进搬出高带宽内存,从而显著提高实际速度并降低中间显存占用。

位置外推是个难题。 正弦位置编码理论上能外推,实际超过训练长度效果就拉胯。现在主流大模型普遍改用 RoPE(旋转位置编码),靠旋转矩阵把相对位置编码进注意力,外推更稳;有的还配合 ALiBi 这种注意力偏置来直接惩罚远距离。

数据效率不如 CNN。 注意力是个「通用插头」,归纳偏置很弱,好处是灵活,坏处是特别吃数据。小数据集上 Transformer 往往打不过 CNN,得靠大规模预训练才能发挥威力——这也是为什么它和「大数据 + 大算力」紧紧绑在了一起。

九、初学者最容易混淆的六件事

Q、K、V 是三个不同的输入吗?

自注意力里不是。它们来自同一份 $X$,只是经过三组不同的可学习投影。交叉注意力里,Q 来自 Decoder,K/V 才来自 Encoder 输出。

Attention 输出的是一个权重矩阵吗?

权重矩阵只是中间结果 $A=softmax(QK^T/\sqrt{d_k})$。注意力子层真正输出的是 $AV$,即每个位置汇总后的新表示。

多头就是把同一个注意力算很多遍吗?

不是简单复制。每个头参数不同,能在不同的表示子空间里建立匹配;拼接之后还要由 $W_O$ 再次混合。

有位置编码后,模型就天然懂词序了吗?

位置编码只给了模型可利用的位置信号。如何把位置与内容组合成语序、距离和结构关系,仍要从训练数据里学。

Decoder 训练时也是一个 token 一个 token 跑吗?

不需要。右移后的完整目标序列可以并行输入,因果 mask 负责阻断未来信息。推理时没有后续真值,才必须自回归生成。

Transformer 层数越多,注意力范围才越大吗?

单层全局自注意力理论上已经能让任意两个位置直接交互。继续堆层主要是为了反复进行「跨 token 路由 + token 内加工」,形成更复杂的组合表示,而不是像普通局部卷积那样单纯扩大感受野。

十、用一套检查表确认自己真的懂了

读完后,可以尝试不看上文回答这些问题:

  1. 给定 $X:[n,d_{model}]$,为什么 $QK^T$ 是 $[n,n]$?它的行和列分别表示什么?
  2. 为什么 Key 和 Value 要分开?注意力权重最终乘的是谁?
  3. 为什么缩放使用 $\sqrt{d_k}$,而不是 $d_k$?
  4. 多头拼接后如何回到 $d_{model}$ 维?
  5. Encoder 自注意力、Decoder 自注意力、交叉注意力的 Q/K/V 分别来自哪里?
  6. 为什么 Decoder 训练能并行,推理却要逐 token?
  7. padding mask 和 causal mask 各自挡住什么?
  8. 注意力负责 token 间交互,那么 FFN 负责什么?
  9. KV Cache 缓存了什么,又没有解决什么?

如果这些能用自己的话说清楚,再去读 PyTorch 的 MultiheadAttention、Hugging Face 模型源码,看到的就不会是一团 reshape、transpose 和 mask,而是本文这条数据流的工程实现。

十一、写在最后

把 Transformer 拆开看,其实没有哪一个零件是全新的:注意力机制之前就有,残差连接是 ResNet 的,LayerNorm 是老技术,FFN 就是普通全连接。它的伟大在于把这些东西用一种恰到好处的方式组合起来,用注意力彻底替代了递归,换来了并行和全局视野。

理解了这套架构,再看 BERT(Encoder 堆叠,双向理解)、GPT(Decoder 堆叠,单向生成)、T5(完整 Encoder-Decoder)、ViT(把图像切块当序列),就都是同一套原理的不同切法。后续那些改进——Flash Attention 提速、RoPE 改位置编码、MoE 把 FFN 换成专家混合——也都是在今天讲的这些零件上动刀。

这篇拆解的意义不在于记住每个公式,而在于建立一个可以随时往里装新东西的骨架。下次看到某个大模型的新 trick,你能立刻定位它改的是注意力、位置编码、归一化还是 FFN,就算没白读。

技术没有终点,但地基是有限的。把 Transformer 这块地基夯实的成本,远比追逐每一个新模型要低,回报却持续得多。

版权声明: 本文首发于 指尖魔法屋-深入拆解 Transformer:从注意力到完整架构https://blog.thinkmoon.cn/post/1034-deep-dive-transformer-architecture/) 转载或引用必须申明原指尖魔法屋来源及源地址!