程序员熊建锟。AI技术知名博主。CSDN优质创作者(粉丝1W+)、腾讯云开发者先锋;阿里云专家博主;华为云“云享专家”、51CTO专家博主
我第一次认真读 Transformer 论文的时候,卡住的地方不是公式,而是一个很朴素的问题:为什么非得让每个词去看所有词?
那时候我对 RNN 的印象还挺好。它读句子像人读书,从左到右,一个词一个词过,状态一路带下去,很符合直觉。Transformer 一上来把循环拆了,让所有位置同时互相看,我当时的反应是:这不乱套了吗?
后来写得多了、调得多了,才慢慢反过来:真正“乱套”的其实是 RNN——不是它读得不对,而是它读得太慢,而且读到后面容易忘前面。Transformer 那次设计,本质上是在回答一个很工程化的问题:如果让每个词直接看见所有词,会怎样?
这篇文章就沿着这个问题走一遍。先从 RNN 的麻烦说起,再讲自注意力的直觉,最后回到它自己的代价,以及今天大家在怎么绕这个代价。尽量不堆公式,多画图。
一、先说说 RNN 到底卡在哪
RNN 的工作方式,用一句话概括:第 t 个位置的计算,必须等第 t−1 个位置算完。
flowchart LR
x1["词1"] --> h1["隐藏状态 h1"]
h1 --> h2["隐藏状态 h2"]
x2["词2"] --> h2
h2 --> h3["隐藏状态 h3"]
x3["词3"] --> h3
h3 --> h4["隐藏状态 h4"]
x4["词4"] --> h4
h4 --> out["输出"]
这条链条本身没什么错,问题出在两个字:必须。
第一,没法并行。GPU 最擅长的是一大批计算同时做,但 RNN 这条链是串起来的,第 4 个位置永远要等前三个。硬件再强,也只能一个一个来。训练一个大模型,时间就是这么被拖出来的。
第二,长距离信息会衰减。句子一长,句首的信息要经过很多次状态传递才能到句尾,中间每一步都可能把它稀释一点。你让模型判断“它”指代的是前文的哪个名词,如果这个名词离得很远,RNN 就很吃力。
CNN 算是一条替代路线,可以并行,但卷积的感受野是局部的,要让顶层看到整个句子,得堆很多层,代价也不小。
2017 年那篇论文给的答案很干脆:循环和卷积都拿掉,只留注意力。 每个位置不再等谁,直接去“看”序列里所有位置。并行有了,长距离路径也短了——从“隔很多步”变成“一步到位”。
二、自注意力:拿问题去档案库查资料
“每个词看所有词”听起来抽象,但它可以落在一个很日常的场景上。
想象你走进一个档案库,手里有一个问题,要去一堆档案柜里找答案。每个柜子上贴着一个标签,告诉你里面大概装了什么。你拿手里的问题去和每个标签比对,匹配度越高,就越该打开那个柜子,取出里面的内容,然后按匹配度加权汇总成你的答案。
自注意力就是这个过程。
flowchart LR
Q["Query(我的问题)"] --> M{"和每个 Key 比对"}
K["Key(每个词的标签)"] --> M
M --> S["相似度分数"]
S --> W["softmax 归一化成权重"]
V["Value(每个词的内容)"] --> A["按权重加权求和"]
W --> A
A --> O["当前词的新表示"]
对应到 Transformer 里:
- Query(Q):当前这个词提出的“问题”;
- Key(K):每个词亮出来的“标签”;
- Value(V):每个词实际提供的“内容”。
Q 和 K 做点积,得到相似度分数;softmax 把分数变成权重;再用权重对 V 加权求和。整个过程就这一套动作,没有循环,没有递归,全是矩阵运算,所以能并行。
举个具体点的例子。句子:
The animal didn't cross the street because it was too tired.
模型处理 “it” 的时候,“it” 的 Query 会和句子里每个词的 Key 比一遍。“animal” 的 Key 匹配度很高,“street” 就没那么高。于是 “animal” 的 Value 被大量聚合进 “it” 的新表示。模型就这样“知道”了 it 指的是 animal。
有个细节我当时忽略了很久:Q、K、V 不是词向量本身,而是词向量分别乘了三个可学习矩阵之后的结果。 也就是说,“怎么提问、亮什么标签、提供什么内容”,是模型在训练里自己学出来的。这一点挺关键——它意味着注意力不是人工规则,而是可训练的参数化行为。
三、一个注意力不够用,所以有了多头
如果只有一组 Q、K、V,模型只能学到一种“关注方式”。但语言里的关系是多种多样的:指代是一种,句法依存是一种,语义相似又是一种。一组注意力很难同时把这些都编码好。
多头注意力的做法是:并行跑 h 组独立的 Q、K、V,每组在不同的表示子空间里算注意力,最后把 h 个结果拼起来,再做一次线性映射。
flowchart TD
In["输入表示"] --> H1["头 1:Q1 K1 V1"]
In --> H2["头 2:Q2 K2 V2"]
In --> H3["头 3:Q3 K3 V3"]
In --> Hn["头 h:Qh Kh Vh"]
H1 --> C["拼接"]
H2 --> C
H3 --> C
Hn --> C
C --> P["线性映射"]
P --> Out["多头注意力输出"]
它增加的不是“同一件事做得更强”,而是 “能同时做几件不同的事”。这是 Transformer 表达能力的重要来源之一,也是为什么后来大家发现不同注意力头确实在关注不同模式——有的盯指代,有的盯相邻词,有的盯句法结构。
四、两个不显眼、但不能少的东西
讲 Transformer 的文章大多把重点放在自注意力上,但有两个设计经常被一句带过,其实缺了它们,整个架构根本跑不起来。
第一个是位置编码。
Transformer 没有循环结构,模型本身不知道词序。“猫追狗”和“狗追猫”在它眼里是同一个词集合。位置编码就是用来把顺序信息注入模型的:原始论文用正弦/余弦函数生成固定位置向量,叠加在词嵌入上;后来的工作也大量使用可学习的位置嵌入。没有它,注意力会把句子当成一袋词。
第二个是残差连接 + 层归一化(Add & Norm)。
每个子层(自注意力或前馈网络)的输出,都会和子层输入相加,再做 LayerNorm。这个设计不负责“看什么”,但负责让深层网络稳定训练。我自己的体会是:Self-Attention 决定了 Transformer 能“看到什么”,Add & Norm 决定了 Transformer 能不能“堆得深”。 没有残差,深层注意力网络的梯度很难传稳。
五、编码器和解码器,各管一段
原始 Transformer 是编码器—解码器结构。
编码器由 N 个相同层堆叠,每层两个子层:多头自注意力和逐位置前馈网络,每个子层都带残差和层归一化。它的任务是理解输入。
解码器多了一个关键约束:掩码自注意力。生成第 t 个词时,只能看第 1 到第 t−1 个位置,不能偷看未来。除此之外,解码器还有一个编码器—解码器注意力子层,用来对齐编码器的输出。
flowchart LR
subgraph Enc["编码器(N 层)"]
E1["多头自注意力"] --> E2["前馈网络"]
end
subgraph Dec["解码器(N 层)"]
D1["掩码多头自注意力"] --> D2["编码器-解码器注意力"]
D2 --> D3["前馈网络"]
end
Enc --> D2
Dec --> O["输出"]
后来大模型大多采用 decoder-only 结构,但“注意力 + 前馈网络 + 残差归一化”这套骨架一直保留着。变的是组合方式,不是基本零件。
六、优雅的背面:O(L²)
到这里,自注意力的优雅已经很清楚了:每个词直接和所有词交互,路径短,可并行,表达能力强。
但优雅是有价格的。
序列长度为 L 时,注意力要算一个 L×L 的分数矩阵。计算复杂度是 O(L²),注意力矩阵本身也是 O(L²) 的空间开销。 上下文翻倍,矩阵运算大致翻四倍。
flowchart LR
L1["长度 L"] --> M1["L × L 注意力矩阵"]
L2["长度 2L"] --> M2["2L × 2L 矩阵 ≈ 4 倍开销"]
L3["长度 4L"] --> M3["4L × 4L 矩阵 ≈ 16 倍开销"]
短上下文时代,这个平方项不是主要矛盾,大家更关心模型效果。但今天的情况变了:推理模型要输出很长的思维链,Agent 要在上下文里保留大量工具调用结果,代码库要整体塞进提示词。长上下文从边缘场景变成了主旋律,平方项也从学术脚注变成了 GPU 账单上最显眼的一行。
而且成本的大头往往不在“读提示词”,而在“逐个生成 Token”的解码阶段。解码是内存带宽密集型的:每生成一个新 Token,GPU 都要把整个 KV 缓存从高带宽内存里重新读一遍。张量核心算得很快,剩下时间都在等内存。这就是为什么长输出比长输入贵得多,也是 KV Cache 成为推理优化核心对象的原因。
七、今天大家在怎么绕这个代价
面对 O(L²),业界没有干等硬件变快,而是在架构上分出了几条路。大致是这么个分叉:
flowchart TD
Root["O(L²) 的代价"] --> S["稀疏注意力:只算重要的词对"]
Root --> Lin["线性注意力:把平方压到线性"]
Root --> SSM["状态空间模型:用线性递归替代注意力"]
Root --> Hy["混合架构:少量精确注意力 + 廉价机制"]
S --> Goal["目标:保留精确召回,同时降成本"]
Lin --> Goal
SSM --> Goal
Hy --> Goal
稀疏注意力的思路是:不是所有词对都值得算,只保留真正重要的那些。线性注意力去掉 softmax,把注意力坍缩成固定大小的循环状态,复杂度从平方降到线性。状态空间模型(比如 Mamba 系列) 走得更远,直接用可选择的线性递归取代注意力,长序列效率很高,但在上下文学习和多任务泛化上曾经不太稳。
混合架构是我个人觉得最有意思的一条:它不追求“一种机制通吃”,而是保留少量精确注意力层负责召回,其余部分用更便宜的线性或 SSM 机制。SSM 抓局部上下文,Transformer 层解决全局依赖,按需组合。
这些路线不是在“推翻 Transformer”,而是在拆掉一个默认设定:“每一层都必须用完整的自注意力” 这件事,不再是唯一合理的工程选择。
八、回到最开始那个问题
现在再回看我一开始的困惑——“为什么非得让每个词看所有词”——答案其实分两层。
第一层是能力层面:让每个词直接看见所有词,确实解决了 RNN 的串行瓶颈和长距离衰减,带来了并行和更强的全局建模能力。这是 Transformer 站住脚的根本。
第二层是成本层面:这个设计本身很贵,而且越到长上下文时代越贵。所以今天的架构演进,本质是在保留“精确全局召回”的同时,想办法让这件事变便宜。
我的判断是,与其问“Transformer 会不会被取代”,不如问得更具体一点:Transformer 的哪些部分不可替代,哪些部分正在被重新组装。 自注意力作为精确检索和长程依赖的机制,在很多任务里仍然难以完全替代;但“每层都用完整自注意力”这个 2017 年的默认设定,已经在被各种混合方案改写。
理解 Transformer 的起点,其实可以很简单:它让每个词直接看见所有词。 理解了这句话的好处,再理解它的代价,后面的稀疏、线性、SSM、混合,就都有了落脚点。