← 课程首页 | M0 理论地基 论文深度拆解
NeurIPS 2017 · Google Brain · 引用超 17 万次

Attention Is
All You Need

你需要的只是注意力

这篇 8 页的论文是现代 AI 的地基。BEVFormer、UniAD、VLA、GPT、FSD v14——全部建在它之上。本文把它彻底拆开:每个矩阵、每次相乘、每个设计取舍,都讲清楚为什么。

核心机制 Self-Attention 参数维度 d_model=512 层数 N=6 注意力头 h=8
00 / 动机

在它之前,处理序列有个结构性绝症

2017 年之前,处理"一句话"这种序列数据,主流是 RNN / LSTM。它们的工作方式像读书——一个词一个词往下读,读到第 5 个词时,脑子里装的是前 4 个词压缩成的一个"记忆状态"。

这套机制有两个致命问题:

病灶一 · 无法并行

要算第 100 个词,必须先算完前 99 个——天然串行。GPU 有几千个核心,但在 RNN 面前只能排队干活。训练一个大模型要几个月,这在工程上不可接受。

病灶二 · 长距离依赖丢失

"那只动物没有过马路,因为它太累了"——"它"指代"动物",两者隔了 8 个词。RNN 要把"动物"的信息一路传递 8 步才能到"它",中间经过 8 次压缩,信息基本损耗殆尽。距离越远,越记不住。

◆ 这篇论文的答案

既然"逐个传递"是问题的根源,那就彻底抛弃递归。让句子里任意两个词直接建立连接——"它"想知道自己指代谁,不用沿着链条传 8 步,而是一步之内直接"看"遍全句所有词,谁相关就重点关注谁。

这就是标题的含义:你不需要循环、不需要卷积,你需要的只是注意力。

代价是什么?丢掉递归也就丢掉了"顺序"——模型不再知道哪个词在前哪个在后。这个坑论文用位置编码补上(第 6 节详解)。这是一个非常典型的工程取舍:用一个可控的小问题,换掉两个致命的大问题。

维度RNN / LSTMTransformer
计算方式串行,逐词推进全并行,一次算完整句
任意两词的距离O(n) 步,逐级传递O(1) 步,直接连接
长依赖衰减严重无衰减
顺序信息天然自带丢失,需位置编码补
计算复杂度O(n·d²)O(n²·d),句子越长越贵

注意最后一行——Transformer 不是全面碾压,它的复杂度是序列长度的平方。这个特性会在第 9 节回到自动驾驶时变得极其重要:BEVFormer 的"序列"是 40000 个 BEV 格子,平方级复杂度直接爆炸,所以它必须用可变形注意力而不是标准注意力。你之前学 BEVFormer 时的那个疑问,答案的根在这里。

01 / 全局架构

先看整栋楼,再进房间

原论文做的是机器翻译(英译德),所以是编码器-解码器结构:左边编码器读懂原文,右边解码器生成译文。两边各堆 6 层。

编码器 Encoder × 6 层 输入嵌入 + 位置编码 多头自注意力 句中每个词看遍全句 +残差 前馈网络 FFN 逐位置独立变换 +残差 编码器输出 全句的语义表示 解码器 Decoder × 6 层 已生成的译文 + 位置编码 带掩码的自注意力 只能看已生成的词,不能偷看未来 交叉注意力 Q 来自译文,K/V 来自原文 编码器输出 作为 K 和 V 前馈网络 FFN 逐位置独立变换 线性层 + Softmax 输出下一个词的概率 数据自下而上流动 · 每个子层外都包着「残差连接 + 层归一化」
图 1 · Transformer 完整架构。编码器读原文,解码器逐词生成译文。紫色那条线是关键:编码器的输出被送进解码器的交叉注意力,充当 K 和 V。
▲ 先记住三种注意力

整个架构里注意力出现了三次,机制完全一样,只是 Q/K/V 的来源不同:

① 编码器自注意力:Q、K、V 全来自原文——原文内部互相理解
② 解码器掩码自注意力:Q、K、V 全来自已生成的译文,且加掩码防止偷看未来
③ 交叉注意力:Q 来自译文,K/V 来自原文——译文去原文里找对应信息

这个"Q 从哪来、K/V 从哪来"的框架极其重要。你学 BEVFormer 时的"空间交叉注意力"就是第 ③ 种:Q 来自 BEV 格子,K/V 来自相机图像。同一个模式,换了个场景。

02 / 核心机制

Q、K、V:三个角色撑起一切

这是全文最核心的一节。理解了 Query / Key / Value 这三个角色,Transformer 就通了一大半。

◆ 图书馆检索

你去图书馆找资料,脑子里有个问题:Query——"我想找关于唐朝经济的书"。
书架上每本书贴着标签:Key——"唐朝政治"、"唐朝经济"、"宋朝文学"。
书里的实际内容是:Value。

流程是:拿你的问题去比对每本书的标签(Q 和 K 算相似度),标签越匹配的书打分越高。然后按分数加权,把所有书的内容混合起来(用权重对 V 加权求和)。匹配度 0.7 的那本贡献 70%,0.05 的那本贡献 5%。

你不是只读一本书,而是"按相关程度"读了所有书。这就是注意力。

在句子里具体是什么

现在把图书馆换成一句话。以"那只动物没有过马路,因为它太累了"为例,处理"它"这个词时:

"它"的 Query 和"动物"的 Key 匹配度很高(都关乎指代关系),于是"动物"的 Value 就在"它"的新表示里占了很大权重。处理完之后,"它"这个词的向量里就融进了"动物"的语义——模型"知道"了它指代什么。

Q、K、V 从哪来?三个可学习的矩阵

关键点:Q、K、V 不是凭空来的,是同一个词向量乘以三个不同的权重矩阵得到的。

词向量 x 512 维 「它」这个词 × W_Q 可学习矩阵 × W_K 可学习矩阵 × W_V 可学习矩阵 Query 我在找什么 Key 我能提供什么 Value 我的实际内容 关键理解 同一个词向量, 乘三个不同矩阵, 得到三个不同角色。 W_Q/W_K/W_V 由训练学出
图 2 · Q/K/V 的来源。同一个词向量 x 分别乘以三个可学习矩阵,扮演三个不同角色。这三个矩阵是模型真正要学的参数——它们决定了"什么算相关"。
● 这里藏着一个深刻的点

"什么词和什么词相关"这件事,不是人写规则定的,是 W_Q 和 W_K 学出来的。训练时模型为了把翻译做对,被迫调整这两个矩阵,让"它"的 Q 和"动物"的 K 算出高分。

这和你之前理解 BEV 格子 256 维语义时的结论是同一个道理:表示不是设计的,是任务逼出来的。

03 / 数学

拿真实数字,手算一遍

论文里那个著名公式:

Attention(Q, K, V) = softmax( Q·Kᵀ / √d_k ) · V

看着吓人,拆开只有四步。我们用一个极简例子:句子只有 3 个词,向量只有 4 维(真实是 512 维,这里缩小方便手算)。

STEP 1 · Q 和每个 K 做点积 → 得到"相关分数"

点积是衡量两个向量"方向有多一致"的最简单方式。方向越一致,值越大,代表越相关。

# 当前在处理「它」这个词,它的 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)——符合预期,"它"确实该关注"动物"。

STEP 2 · 除以 √d_k → 缩放,防止梯度消失

这一步是论文标题里"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

STEP 3 · Softmax → 把分数变成加起来等于 1 的权重

# 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% 给"累"。

STEP 4 · 用权重对 V 加权求和 → 得到新的词表示

# 三个词的 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 行代码。剩下的都是围绕它的工程封装。

04 / 动手

点一个词,看它在关注谁

上一节手算了一遍,这一节让你直接看到结果。点击句子里任意一个词,下面会显示它把注意力权重分配给了哪些词——这正是训练好的 Transformer 内部真实发生的事。

选择一个句子
点击下方任意一个词,查看它的注意力分布
图 3 · 交互式注意力演示。权重基于真实模型的典型行为构造,用于展示注意力的语言学规律。
● 玩的时候注意这几件事

① 点代词("它"/"it"),看它是否把最大权重给了它指代的名词——这就是模型"理解"指代关系的方式。
② 点动词,看它关注的往往是自己的主语和宾语——语法结构被注意力捕捉到了。
③ 注意每一行权重加起来都是 100%——这是 softmax 保证的,注意力是一种"分配有限资源"的机制。

这里有个值得停下来想的点:没有人教过模型"它"是代词、要找先行词。这些语法知识是模型为了把翻译任务做对,自己从数据里学出来的。注意力权重是模型思考过程的可视化——这也是为什么 Transformer 比 RNN 更容易调试和解释。

05 / 多头注意力

一个视角不够,那就来八个

前面讲的是单头注意力。但一个问题来了:语言里的关系有很多种——语法关系、指代关系、语义关系、修饰关系……用一套 Q/K/V 只能捕捉一种"相关性"的定义。

论文的解法很直接:并行搞 8 套。每套有自己的 W_Q / W_K / W_V,各自独立算一遍注意力,最后把 8 个结果拼起来再过一个线性层融合。

输入 512 维 头 1 可能学到「指代关系」 头 2 可能学到「主谓关系」 ⋮ …共 8 个头… 头 7 可能学到「修饰关系」 头 8 可能学到「邻近位置」 拼接 8×64=512 × W_O 融合输出 每个头只用 64 维(512÷8),所以 8 个头的总计算量 ≈ 单个 512 维的头,几乎不增加成本
图 4 · 多头注意力。512 维被切成 8 份各 64 维,每个头在自己的子空间里独立学习一种"相关性",最后拼接融合。
◆ 一个比方

看一场足球比赛,如果只有一个摄像机位,你只能看到一个视角。多机位转播——一个跟球、一个拍全景、一个盯守门员、一个拍观众——把所有视角的画面综合起来,你才真正"看懂"了这场比赛。

多头注意力就是多机位。关键是每个头的机位不是人安排的,是训练中自己学出来的。

▲ 一个常见误解

很多人以为 8 个头 = 8 倍计算量。不是的。512 维被切成 8 份,每头只处理 64 维。8 个 64 维的头,总量约等于 1 个 512 维的头。多头几乎是免费的午餐——同样的算力,换来 8 个不同视角。这是这个设计如此漂亮的原因。

06 / 位置编码

丢掉递归的代价,和它的补丁

回到第 0 节埋的伏笔:抛弃递归换来了并行,但也丢了顺序。

看这个问题:"狗咬人"和"人咬狗"。对纯注意力机制来说,这两句话完全一样——因为注意力计算里没有任何"谁在前谁在后"的信息,它把句子当成一个无序的词袋。

论文的解法:在词向量里直接加上一个表示位置的向量。而且用的是一个很巧妙的构造——不同频率的正弦和余弦函数:

PE(pos, 2i)   = sin( pos / 10000^(2i/d_model) )
PE(pos, 2i+1) = cos( pos / 10000^(2i/d_model) )

# pos = 词在句子里的位置(第几个词)
# i   = 向量的第几维
不同维度使用不同频率的波,组合成每个位置独一无二的「指纹」 维度 0-1 高频 维度 100-101 中频 维度 500-501 低频 位置 3 位置 8 位置 13 在任一竖线处,512 个维度的取值组合,是这个位置独有的编码
图 5 · 正弦位置编码。类似二进制计数——低位翻转快、高位翻转慢,不同频率的组合让每个位置有唯一编码。
◆ 为什么用正弦波这么绕?

最直觉的做法是直接编号 1、2、3……但这有两个问题:句子长了数值会很大(破坏训练稳定性);而且模型见过的最长句子是 50 词的话,第 100 个位置它完全没概念。

正弦编码解决了这两点:数值永远在 -1 到 1 之间;而且因为三角函数的性质,PE(pos+k) 可以由 PE(pos) 线性表示——这让模型能学到"相对位置"关系,从而泛化到训练时没见过的更长句子。

✓ 连回自动驾驶

BEVFormer 里也有位置编码,但含义变了:NLP 编码"第几个词",BEV 编码"第几个格子、在网格的哪个位置"。同样的机制,因为 BEV 网格同样是"打散成序列后丢失了空间位置",需要把 (x, y) 坐标信息重新注入。你之前学的 BEV query 之所以知道自己代表哪块地,靠的就是这个。

07 / 配角

残差、层归一化、前馈网络:不起眼但不能少

注意力是主角,但如果只有注意力,这个模型训不起来。三个"配角"负责让深层网络能稳定训练。

残差连接 —— 给梯度修一条高速公路

每个子层的输出不是 Sublayer(x),而是 x + Sublayer(x)——把输入原封不动加回输出上。

为什么?6 层堆起来,反向传播时梯度要穿过 6 层,每层都可能衰减,到底部就消失了。残差连接相当于给梯度开了一条直达通道:就算中间层学到的东西没用(Sublayer 输出接近 0),信息和梯度也能通过 x 这条路原样传过去。这是深层网络能训起来的关键。

层归一化 —— 把数值拉回稳定区间

经过多层计算,数值分布容易漂移(有的维度特别大、有的特别小),导致训练不稳。层归一化把每个词向量的数值重新归到均值 0、方差 1,再用两个可学习参数做缩放。

所以完整的子层是:LayerNorm(x + Sublayer(x))。

前馈网络 FFN —— 真正存放"知识"的地方

注意力负责"混合信息",FFN 负责"加工信息"。它是两层全连接:

FFN(x) = max(0, x·W₁ + b₁) · W₂ + b₂

# 512 → 2048 → 512
# 先升维到 4 倍,过 ReLU 激活,再降回来
● 一个容易被忽略的事实

FFN 占了 Transformer 约 2/3 的参数量。注意力机制虽然是核心创新,但参数大头在 FFN。近年研究认为 FFN 层实际上在充当"知识库"——大模型记住的事实性知识,很大程度存在这里。

另外注意:FFN 是逐位置独立作用的——每个词各自过一遍同样的 FFN,词与词之间不交互。交互只发生在注意力层。这个分工非常干净:注意力管"看谁",FFN 管"想什么"。

08 / 编码器与解码器

两边的关键差异

解码器为什么要"掩码"

翻译时,解码器是一个词一个词往外吐的。生成第 3 个词时,它只能看到已经生成的第 1、2 个词——不能看到第 4、5 个词,因为那还没生成。

但训练时有个麻烦:为了并行,我们把整句正确译文一次性喂进去。如果不加限制,模型算第 3 个词时会"看到"第 4 个词的答案——这叫作弊,训练时表现完美,实际推理时立刻崩溃(因为那时真没有未来信息)。

掩码就是解决这个:在 softmax 之前,把所有"未来位置"的分数强行设成负无穷,softmax 之后这些位置的权重就是 0。

编码器:每个词能看全句 解码器:只能看已生成的 被看的词 → 当前词 ↓ 全部可见 · 双向理解 被看的词 → 上三角被屏蔽 · 单向生成 深色格子 = 权重强制为 0
图 6 · 注意力掩码。编码器的注意力矩阵是满的(双向),解码器的上三角被屏蔽(只能看过去)。GPT 类模型用的就是右边这种。
▲ 这个区别催生了两大流派

只用编码器(双向看全句)→ BERT 一系,擅长理解类任务(分类、抽取)
只用解码器(单向逐词生成)→ GPT 一系,擅长生成类任务
编码器+解码器(原论文)→ 翻译这类"读一段、写一段"的任务

今天的大模型绝大多数是只用解码器的架构。小鹏 VLA 2.0 砍掉语言层保留的那部分,本质也是这一支。

交叉注意力:整个架构的"翻译"发生在这里

这是解码器独有的一层,也是最能体现 Q/K/V 框架威力的地方:

于是解码器每生成一个词,都会去原文里"查"一遍,找到最相关的部分。这就是神经机器翻译中"对齐"的实现方式——不需要人工写对齐规则,注意力自动学会了。

✓ 这就是 BEVFormer 空间交叉注意力的原型

对照一下你已经学过的:BEVFormer 的 Q 来自 BEV 网格格子,K/V 来自 6 路相机的图像特征。格子"去图像里查"自己对应位置有什么——和译文"去原文里查"对应内容,是完全同构的机制。

唯一的区别是 BEVFormer 用几何投影把查询范围缩小到了投影点附近(可变形注意力),因为 40000 个格子 × 全图像素的标准注意力算不动——这正呼应第 0 节讲的 O(n²) 复杂度问题。

09 / 回到智驾

这篇 2017 年的论文,怎么开上了车

现在把整条线串起来。你学过的每一个自动驾驶模型,都能在这篇论文里找到根。

Transformer 概念在自动驾驶里变成了什么出现在
Token(词)BEV 网格的一个格子 / 一个检测目标 / 一帧图像块BEVFormer、UniAD
d_model = 512embed_dims = 256(BEV 格子的语义维度)BEVFormer
自注意力时序自注意力:当前帧 BEV 关注历史帧 BEVBEVFormer
交叉注意力空间交叉注意力:BEV 格子查询相机图像BEVFormer
Query 概念Track Query / Map Query / Ego QueryUniAD
位置编码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。画得出来,说明核心通了。

自测二 · 说清三种注意力的 Q/K/V 分别来自哪

编码器自注意力、解码器掩码自注意力、交叉注意力。这是面试高频题。

自测三 · 手敲一遍 attention 函数

就是第 3 节那 6 行。按你的"必须手敲"清单,这个属于必敲——它是所有模型 forward 里最核心的部件。

自测四 · 回头重读 BEVFormer 的 SCA 代码

对照本文第 8 节,确认你能一眼认出"哪部分是 Q、哪部分是 K/V、投影在哪一步发生"。