这篇 8 页的论文是现代 AI 的地基。BEVFormer、UniAD、VLA、GPT、FSD v14——全部建在它之上。本文把它彻底拆开:每个矩阵、每次相乘、每个设计取舍,都讲清楚为什么。
2017 年之前,处理"一句话"这种序列数据,主流是 RNN / LSTM。它们的工作方式像读书——一个词一个词往下读,读到第 5 个词时,脑子里装的是前 4 个词压缩成的一个"记忆状态"。
这套机制有两个致命问题:
要算第 100 个词,必须先算完前 99 个——天然串行。GPU 有几千个核心,但在 RNN 面前只能排队干活。训练一个大模型要几个月,这在工程上不可接受。
"那只动物没有过马路,因为它太累了"——"它"指代"动物",两者隔了 8 个词。RNN 要把"动物"的信息一路传递 8 步才能到"它",中间经过 8 次压缩,信息基本损耗殆尽。距离越远,越记不住。
既然"逐个传递"是问题的根源,那就彻底抛弃递归。让句子里任意两个词直接建立连接——"它"想知道自己指代谁,不用沿着链条传 8 步,而是一步之内直接"看"遍全句所有词,谁相关就重点关注谁。
这就是标题的含义:你不需要循环、不需要卷积,你需要的只是注意力。
代价是什么?丢掉递归也就丢掉了"顺序"——模型不再知道哪个词在前哪个在后。这个坑论文用位置编码补上(第 6 节详解)。这是一个非常典型的工程取舍:用一个可控的小问题,换掉两个致命的大问题。
| 维度 | RNN / LSTM | Transformer |
|---|---|---|
| 计算方式 | 串行,逐词推进 | 全并行,一次算完整句 |
| 任意两词的距离 | O(n) 步,逐级传递 | O(1) 步,直接连接 |
| 长依赖 | 衰减严重 | 无衰减 |
| 顺序信息 | 天然自带 | 丢失,需位置编码补 |
| 计算复杂度 | O(n·d²) | O(n²·d),句子越长越贵 |
注意最后一行——Transformer 不是全面碾压,它的复杂度是序列长度的平方。这个特性会在第 9 节回到自动驾驶时变得极其重要:BEVFormer 的"序列"是 40000 个 BEV 格子,平方级复杂度直接爆炸,所以它必须用可变形注意力而不是标准注意力。你之前学 BEVFormer 时的那个疑问,答案的根在这里。
原论文做的是机器翻译(英译德),所以是编码器-解码器结构:左边编码器读懂原文,右边解码器生成译文。两边各堆 6 层。
整个架构里注意力出现了三次,机制完全一样,只是 Q/K/V 的来源不同:
① 编码器自注意力:Q、K、V 全来自原文——原文内部互相理解
② 解码器掩码自注意力:Q、K、V 全来自已生成的译文,且加掩码防止偷看未来
③ 交叉注意力:Q 来自译文,K/V 来自原文——译文去原文里找对应信息
这个"Q 从哪来、K/V 从哪来"的框架极其重要。你学 BEVFormer 时的"空间交叉注意力"就是第 ③ 种:Q 来自 BEV 格子,K/V 来自相机图像。同一个模式,换了个场景。
这是全文最核心的一节。理解了 Query / Key / Value 这三个角色,Transformer 就通了一大半。
你去图书馆找资料,脑子里有个问题:Query——"我想找关于唐朝经济的书"。
书架上每本书贴着标签:Key——"唐朝政治"、"唐朝经济"、"宋朝文学"。
书里的实际内容是:Value。
流程是:拿你的问题去比对每本书的标签(Q 和 K 算相似度),标签越匹配的书打分越高。然后按分数加权,把所有书的内容混合起来(用权重对 V 加权求和)。匹配度 0.7 的那本贡献 70%,0.05 的那本贡献 5%。
你不是只读一本书,而是"按相关程度"读了所有书。这就是注意力。
现在把图书馆换成一句话。以"那只动物没有过马路,因为它太累了"为例,处理"它"这个词时:
"它"的 Query 和"动物"的 Key 匹配度很高(都关乎指代关系),于是"动物"的 Value 就在"它"的新表示里占了很大权重。处理完之后,"它"这个词的向量里就融进了"动物"的语义——模型"知道"了它指代什么。
关键点:Q、K、V 不是凭空来的,是同一个词向量乘以三个不同的权重矩阵得到的。
"什么词和什么词相关"这件事,不是人写规则定的,是 W_Q 和 W_K 学出来的。训练时模型为了把翻译做对,被迫调整这两个矩阵,让"它"的 Q 和"动物"的 K 算出高分。
这和你之前理解 BEV 格子 256 维语义时的结论是同一个道理:表示不是设计的,是任务逼出来的。
论文里那个著名公式:
Attention(Q, K, V) = softmax( Q·Kᵀ / √d_k ) · V
看着吓人,拆开只有四步。我们用一个极简例子:句子只有 3 个词,向量只有 4 维(真实是 512 维,这里缩小方便手算)。
点积是衡量两个向量"方向有多一致"的最简单方式。方向越一致,值越大,代表越相关。
# 当前在处理「它」这个词,它的 Query 是 q
q = [0.8, 0.2, 0.5, 0.1] # 「它」的 Query
# 句中三个词的 Key
k_动物 = [0.9, 0.1, 0.6, 0.2]
k_马路 = [0.1, 0.7, 0.2, 0.5]
k_累 = [0.3, 0.4, 0.1, 0.6]
# 点积:对应位相乘再相加
q·k_动物 = 0.8×0.9 + 0.2×0.1 + 0.5×0.6 + 0.1×0.2 = 1.06
q·k_马路 = 0.8×0.1 + 0.2×0.7 + 0.5×0.2 + 0.1×0.5 = 0.37
q·k_累 = 0.8×0.3 + 0.2×0.4 + 0.5×0.1 + 0.1×0.6 = 0.43
「动物」得分最高(1.06)——符合预期,"它"确实该关注"动物"。
这一步是论文标题里"Scaled"的由来。为什么要除?因为维度越高,点积的数值越容易变得很大。数值一大,下一步的 softmax 会被推到极端(一个接近 1,其余接近 0),梯度几乎为零,模型学不动。
# d_k = 4(我们的向量维度),√4 = 2
1.06 / 2 = 0.53
0.37 / 2 = 0.185
0.43 / 2 = 0.215
# 真实模型 d_k=64,除以 8
# softmax: 先取 exp,再归一化
exp(0.53) = 1.699
exp(0.185) = 1.203
exp(0.215) = 1.240
总和 = 4.142
权重_动物 = 1.699 / 4.142 = 0.41 # 41%
权重_马路 = 1.203 / 4.142 = 0.29 # 29%
权重_累 = 1.240 / 4.142 = 0.30 # 30%
这三个数就是注意力权重——"它"分配 41% 的注意力给"动物",29% 给"马路",30% 给"累"。
# 三个词的 Value
v_动物 = [1.0, 0.2, 0.8, 0.1]
v_马路 = [0.2, 0.9, 0.1, 0.4]
v_累 = [0.3, 0.5, 0.2, 0.9]
# 加权求和
输出 = 0.41×v_动物 + 0.29×v_马路 + 0.30×v_累
= [0.41+0.058+0.09, 0.082+0.261+0.15, ...]
= [0.558, 0.493, 0.417, 0.352]
这个新向量就是"它"经过一层注意力之后的新表示——它不再只是"它"这个孤立的词,而是融合了全句上下文、尤其重点吸收了"动物"信息的向量。
上面为了讲清楚,只算了"它"一个词。真实计算中,句子里所有词的 Q 堆成一个矩阵,一次矩阵乘法就同时算完所有词——这就是"可并行"的含义,也是 GPU 能吃满的原因。
# 完整实现,就这么几行
import torch, math
def attention(Q, K, V, mask=None):
d_k = Q.size(-1)
scores = Q @ K.transpose(-2,-1) / math.sqrt(d_k) # STEP 1+2
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9) # 掩码(解码器用)
weights = torch.softmax(scores, dim=-1) # STEP 3
return weights @ V, weights # STEP 4
整个 Transformer 最核心的部分,就是这 6 行代码。剩下的都是围绕它的工程封装。
上一节手算了一遍,这一节让你直接看到结果。点击句子里任意一个词,下面会显示它把注意力权重分配给了哪些词——这正是训练好的 Transformer 内部真实发生的事。
① 点代词("它"/"it"),看它是否把最大权重给了它指代的名词——这就是模型"理解"指代关系的方式。
② 点动词,看它关注的往往是自己的主语和宾语——语法结构被注意力捕捉到了。
③ 注意每一行权重加起来都是 100%——这是 softmax 保证的,注意力是一种"分配有限资源"的机制。
这里有个值得停下来想的点:没有人教过模型"它"是代词、要找先行词。这些语法知识是模型为了把翻译任务做对,自己从数据里学出来的。注意力权重是模型思考过程的可视化——这也是为什么 Transformer 比 RNN 更容易调试和解释。
前面讲的是单头注意力。但一个问题来了:语言里的关系有很多种——语法关系、指代关系、语义关系、修饰关系……用一套 Q/K/V 只能捕捉一种"相关性"的定义。
论文的解法很直接:并行搞 8 套。每套有自己的 W_Q / W_K / W_V,各自独立算一遍注意力,最后把 8 个结果拼起来再过一个线性层融合。
看一场足球比赛,如果只有一个摄像机位,你只能看到一个视角。多机位转播——一个跟球、一个拍全景、一个盯守门员、一个拍观众——把所有视角的画面综合起来,你才真正"看懂"了这场比赛。
多头注意力就是多机位。关键是每个头的机位不是人安排的,是训练中自己学出来的。
很多人以为 8 个头 = 8 倍计算量。不是的。512 维被切成 8 份,每头只处理 64 维。8 个 64 维的头,总量约等于 1 个 512 维的头。多头几乎是免费的午餐——同样的算力,换来 8 个不同视角。这是这个设计如此漂亮的原因。
回到第 0 节埋的伏笔:抛弃递归换来了并行,但也丢了顺序。
看这个问题:"狗咬人"和"人咬狗"。对纯注意力机制来说,这两句话完全一样——因为注意力计算里没有任何"谁在前谁在后"的信息,它把句子当成一个无序的词袋。
论文的解法:在词向量里直接加上一个表示位置的向量。而且用的是一个很巧妙的构造——不同频率的正弦和余弦函数:
PE(pos, 2i) = sin( pos / 10000^(2i/d_model) )
PE(pos, 2i+1) = cos( pos / 10000^(2i/d_model) )
# pos = 词在句子里的位置(第几个词)
# i = 向量的第几维
最直觉的做法是直接编号 1、2、3……但这有两个问题:句子长了数值会很大(破坏训练稳定性);而且模型见过的最长句子是 50 词的话,第 100 个位置它完全没概念。
正弦编码解决了这两点:数值永远在 -1 到 1 之间;而且因为三角函数的性质,PE(pos+k) 可以由 PE(pos) 线性表示——这让模型能学到"相对位置"关系,从而泛化到训练时没见过的更长句子。
BEVFormer 里也有位置编码,但含义变了:NLP 编码"第几个词",BEV 编码"第几个格子、在网格的哪个位置"。同样的机制,因为 BEV 网格同样是"打散成序列后丢失了空间位置",需要把 (x, y) 坐标信息重新注入。你之前学的 BEV query 之所以知道自己代表哪块地,靠的就是这个。
注意力是主角,但如果只有注意力,这个模型训不起来。三个"配角"负责让深层网络能稳定训练。
每个子层的输出不是 Sublayer(x),而是 x + Sublayer(x)——把输入原封不动加回输出上。
为什么?6 层堆起来,反向传播时梯度要穿过 6 层,每层都可能衰减,到底部就消失了。残差连接相当于给梯度开了一条直达通道:就算中间层学到的东西没用(Sublayer 输出接近 0),信息和梯度也能通过 x 这条路原样传过去。这是深层网络能训起来的关键。
经过多层计算,数值分布容易漂移(有的维度特别大、有的特别小),导致训练不稳。层归一化把每个词向量的数值重新归到均值 0、方差 1,再用两个可学习参数做缩放。
所以完整的子层是:LayerNorm(x + Sublayer(x))。
注意力负责"混合信息",FFN 负责"加工信息"。它是两层全连接:
FFN(x) = max(0, x·W₁ + b₁) · W₂ + b₂
# 512 → 2048 → 512
# 先升维到 4 倍,过 ReLU 激活,再降回来
FFN 占了 Transformer 约 2/3 的参数量。注意力机制虽然是核心创新,但参数大头在 FFN。近年研究认为 FFN 层实际上在充当"知识库"——大模型记住的事实性知识,很大程度存在这里。
另外注意:FFN 是逐位置独立作用的——每个词各自过一遍同样的 FFN,词与词之间不交互。交互只发生在注意力层。这个分工非常干净:注意力管"看谁",FFN 管"想什么"。
翻译时,解码器是一个词一个词往外吐的。生成第 3 个词时,它只能看到已经生成的第 1、2 个词——不能看到第 4、5 个词,因为那还没生成。
但训练时有个麻烦:为了并行,我们把整句正确译文一次性喂进去。如果不加限制,模型算第 3 个词时会"看到"第 4 个词的答案——这叫作弊,训练时表现完美,实际推理时立刻崩溃(因为那时真没有未来信息)。
掩码就是解决这个:在 softmax 之前,把所有"未来位置"的分数强行设成负无穷,softmax 之后这些位置的权重就是 0。
只用编码器(双向看全句)→ BERT 一系,擅长理解类任务(分类、抽取)
只用解码器(单向逐词生成)→ GPT 一系,擅长生成类任务
编码器+解码器(原论文)→ 翻译这类"读一段、写一段"的任务
今天的大模型绝大多数是只用解码器的架构。小鹏 VLA 2.0 砍掉语言层保留的那部分,本质也是这一支。
这是解码器独有的一层,也是最能体现 Q/K/V 框架威力的地方:
于是解码器每生成一个词,都会去原文里"查"一遍,找到最相关的部分。这就是神经机器翻译中"对齐"的实现方式——不需要人工写对齐规则,注意力自动学会了。
对照一下你已经学过的:BEVFormer 的 Q 来自 BEV 网格格子,K/V 来自 6 路相机的图像特征。格子"去图像里查"自己对应位置有什么——和译文"去原文里查"对应内容,是完全同构的机制。
唯一的区别是 BEVFormer 用几何投影把查询范围缩小到了投影点附近(可变形注意力),因为 40000 个格子 × 全图像素的标准注意力算不动——这正呼应第 0 节讲的 O(n²) 复杂度问题。
现在把整条线串起来。你学过的每一个自动驾驶模型,都能在这篇论文里找到根。
| Transformer 概念 | 在自动驾驶里变成了什么 | 出现在 |
|---|---|---|
| Token(词) | BEV 网格的一个格子 / 一个检测目标 / 一帧图像块 | BEVFormer、UniAD |
| d_model = 512 | embed_dims = 256(BEV 格子的语义维度) | BEVFormer |
| 自注意力 | 时序自注意力:当前帧 BEV 关注历史帧 BEV | BEVFormer |
| 交叉注意力 | 空间交叉注意力:BEV 格子查询相机图像 | BEVFormer |
| Query 概念 | Track Query / Map Query / Ego Query | UniAD |
| 位置编码 | BEV 网格的空间坐标编码 | BEVFormer、VAD |
| 解码器逐词生成 | 逐点生成未来轨迹路径点 | VAD、VLA |
| 多模态 token 拼接 | 视觉 token + 导航 token 一起进模型 | VLA、FSD v14 |
"从 Attention Is All You Need 到 BEVFormer,变的是 token 的定义(词 → BEV 格子)和 注意力的稀疏化方式(全局 → 可变形),不变的是 Q/K/V 这个查询-匹配-聚合的核心范式。理解了这一点,看任何新的智驾模型都能快速定位它的创新点在哪。"
Q·Kᵀ → 除以 √d_k → softmax → 乘 V。画得出来,说明核心通了。
编码器自注意力、解码器掩码自注意力、交叉注意力。这是面试高频题。
就是第 3 节那 6 行。按你的"必须手敲"清单,这个属于必敲——它是所有模型 forward 里最核心的部件。
对照本文第 8 节,确认你能一眼认出"哪部分是 Q、哪部分是 K/V、投影在哪一步发生"。