学习进度 · 登录后可记录
课程定位:大模型微调课程的核心理论基石
学习方式:精读课程 理论来源:Transformer模型基础知识 + Attention Is All You Need 原文
前置知识:Module 0(大模型发展史)
💡 在开始之前,花 30 秒把这个目录过一遍。
| 章节 | 主题 | 核心问题 | 难度 |
|---|---|---|---|
| 一 | Seq2Seq 模型:一切的开始 | 编码器-解码器是怎么打包和拆包的? | ⭐⭐ |
| 二 | 分词与嵌入层 | 计算机怎么把文字变成它能算的东西? | ⭐⭐ |
| 三 | 自注意力机制 | Q/K/V 到底是什么鬼?Attention 在 Attention 个啥? | ⭐⭐⭐ |
| 四 | 多头注意力 | 为什么多头比单头好?每个头在看什么? | ⭐⭐⭐ |
| 五 | 残差连接与层归一化 | 深层网络怎么就不会梯度爆炸/消失? | ⭐⭐ |
| 六 | 位置编码 | Attention 不管顺序,那它怎么知道"我爱你"不是"你爱我"? | ⭐⭐⭐ |
| 七 | 完整 Transformer 架构 | 所有零件组装起来是怎么工作的? | ⭐⭐⭐ |
| 八 | Transformer vs RNN/LSTM | 什么时候用 Transformer,什么时候 RNN 还能一战? | ⭐⭐ |
| 九 | 工程实现要点 | 训练这玩意儿要多少显存?有哪些优化黑科技? | ⭐⭐⭐⭐ |
| 目标层级 | 你能做到的事 |
|---|---|
| 记忆 | 掰着手指头说出 Transformer 四大核心组件 |
| 理解 | 跟室友解释清楚 Q/K/V 是什么,并且不把他说睡着 |
| 分析 | 对比 Self-Attention 和 Multi-Head Attention 的区别 |
| 评价 | 评估 Transformer 相比 RNN/LSTM 的优缺点,不偏不倚 |
| 创造 | 拿起笔,从头推导一遍 Scaled Dot-Product Attention |
先镇楼一句话:
Transformer 的本质,是一个"让序列中每个位置都能直接看到所有其他位置"的架构。
这不是我编的,这是数学事实。下面听我细细道来。
学 Transformer 之前,你得先知道它解决的是什么问题。不然你学起来就没有"痛点感",学完了也不知道好在哪。
好,现在想象一个任务:机器翻译。
英文:"The person whose house was bought by John last week moved to Beijing."
中文:"上周买下房子的人搬到了北京。"这句话挺长的,主语"The person"离谓语"moved"差了十万八千里。
RNN 怎么处理的?它是一个词一个词往后看的:
RNN 的信息传递(想象接龙游戏):
位置1(The) → 记住"The"
位置2(person)→ 结合位置1,记住"person"
位置3(whose) → 结合位置1、2,记住"whose"
位置4(house) → 结合位置1、2、3,记住"house"
... ...
位置15(moved)→ 结合前面所有词,记住"moved"
问题来了:位置15还记得位置1说的"The"吗?
→ 大概率早忘了!因为信息要传15次,每次都衰减一点
→ 这就是深度学习里臭名昭著的"长期依赖问题"(Long-Term Dependency)RNN 处理"我爱你"和"你爱我"的时候,可能得到几乎一样的向量表示——因为它对顺序不敏感。
😫 RNN 的痛点:信息传递路径太长,远的词早就记不住了。
Transformer 的核心创新就一句话:我不需要一步步传,我让大家直接对话。
Transformer 的信息传递(想象微信群):
位置1(The person ...) ──发消息──► 位置15(moved)
位置2(person ...) ──发消息──► 位置15(moved)
位置5(house ...) ──发消息──► 位置15(moved)
... ──发消息──► 位置15(moved)
位置15:大家都跟我说了什么?
→ 我(位置15)综合所有人的意见,生成"moved"
→ 所有位置之间都是"直连"的!没有中间商!这就是论文标题 "Attention Is All You Need" 的意思:不需要循环结构,只需要注意力机制。
🎉 革命性的点在哪?信息传递路径从 O(n) 变成了 O(1)。不管句子多长,所有词之间的距离都一样。
Seq2Seq(Sequence-to-Sequence)模型是 Transformer 的老祖宗。咱们从最经典的应用——机器翻译——说起。
输入(英文):"I love AI"
输出(中文):"我爱AI"逐词硬翻,看完一个词翻译一个词,完全不过脑子:
传统逐词翻译:
"I" → "我"
"love" → "爱"
"AI" → "AI"
→ 输出:"我 爱 AI"(中文语序全乱了)Seq2Seq 的核心思想就两个步骤:
Seq2Seq 工作流程(像做读书笔记再复述):
压缩阶段:
"我 love AI 的所有信息" → 📦 知识球(context vector)→ "I love AI"
解压阶段:
📦 知识球 → 生成"我" → 生成"爱" → 生成"AI" → 完成!这样做的好处:翻译"love"的时候,不光看"love"自己,还能看整个句子的信息——这样才能翻得准。
Seq2Seq 模型由两个核心组件构成:编码器(Encoder) 和 解码器(Decoder),形象地说就是两兄弟:

输入:"I love AI"
Step 1: 分词 → ["I", "love", "AI"]
Step 2: Embedding → 每个词变成向量
["I" → v₁, "love" → v₂, "AI" → v₃]
Step 3: 逐个通过 LSTM:
t=0: LSTM(v₁, h₀) → h₁ ← 处理完"I",得出第一个记忆
t=1: LSTM(v₂, h₁) → h₂ ← 结合"love"和之前的信息,更新记忆
t=2: LSTM(v₃, h₂) → h₃ ← 结合"AI"和所有历史,得到最终记忆
最终输出:h₃ = context_vector
→ 把"I love AI"这句话的所有信息,压缩进一个向量里初始状态:h_0 = context_vector(接收到编码器传来的知识球)
Step 1: 输入<BOS>(开始符号)
→ LSTM → "我"
Step 2: 输入"我"(上一步的输出)
→ LSTM → "爱"
Step 3: 输入"爱"
→ LSTM → "AI"
Step 4: 输入"AI"
→ LSTM → <EOS>(结束符号,停止生成)解码器推理的时候,每一步的输入是上一步的输出。如果某一步错了,后面的就全歪了:
正确路径:
<BOS> → "我" → "爱" → "AI" → <EOS>
如果"爱"预测错了:
<BOS> → "我" → "喜欢"(错了!)
继续输入"喜欢":→ "你" → "..." → 彻底歪楼!这叫什么?错误级联传播。一步歪,步步歪。
核心思想:训练的时候,把真实答案告诉模型,让它沿着正确答案走;推理的时候再用它自己的预测。
训练阶段(有Teacher Forcing):
<BOS> → 模型预测"爱" → 但我们强行喂给它"爱"(真实标签)→ 下一步
"爱" → 模型预测"AI" → 强行喂"AI" → 下一步
"AI" → 模型预测<EOS> → 停止
推理阶段(没有Teacher Forcing):
<BOS> → 预测"爱" → 用"爱"预测下一步
"爱" → 预测"你" → 用"你"预测下一步(万一错了就歪了)
...📝 为什么训练可以用正确答案? 因为训练的时候我们知道答案,推理的时候模型只能靠自己。就像考试前有标准答案可以看,真正上考场就只能靠真本事了。
Seq2Seq 虽然解决了逐词硬翻的问题,但它自己也有硬伤:
Seq2Seq 信息传递路径:
位置 1 → h₁
位置 2 → h₂ → h₂
位置 3 → h₃ → h₂ → h₃
位置 4 → h₄ → h₃ → h₃ → h₄
...
位置 N → h_N ──────────────────────────────────→ [context_vector]
问题:1000个词的信息,全部塞进1个向量里!
→ 就像把一整本《战争与和平》压缩成一条微博
→ 必然丢失大量信息!| 瓶颈 | 啥情况 | 后果 |
|---|---|---|
| 信息 bottleneck | 全部信息压进一个向量 | 长句子翻译质量稀烂 |
| 梯度消失 | 反向传播走15步才到头 | 深层网络根本训不动 |
| 无法并行 | 必须一个词一个词按顺序处理 | GPU 利用率极低 |
| 位置丢失 | RNN 分不清"我爱你"和"你爱我" | 语义混淆 |
RNN 的问题在于:它对顺序不敏感!
处理"I love you":
Step 1: "I" → h₁
Step 2: "love" + h₁ → h₂
Step 3: "you" + h₂ → h₃
处理"You love I":
Step 1: "You" → h₁'
Step 2: "love" + h₁' → h₂'
Step 3: "I" + h₂' → h₃'
h₃ 和 h₃' 可能几乎相同!
→ RNN 根本没意识到这两个句子的主语和宾语调换了!😱 所以 RNN 其实是个"健忘症患者"——近期的信息记得清楚,很久以前的信息早忘干净了。
计算机只认数字,不认字。所以我们得先把文字转换成数字。
自然语言:"I love AI"
↓ 【分词 Tokenization】
Token序列:[42, 1523, 8890] ← 词表里的编号(身份证号)
↓ 【Embedding 层】
向量序列:[[0.2, -0.5, ...], [0.8, 0.1, ...], [-0.3, 0.7, ...]]
← 每个词变成了一个512维的稠密向量分词方法不是一天形成的,是逐步进化的:


BPE(Byte Pair Encoding)的核心:找最常见的"邻居",把它们撮合
语料:["low", "lower", "new", "lowest", "newer"]
Step 1:统计所有"邻居对"出现频率
"lo": 2次(low里)
"ow": 2次(low里)
"er": 2次(lower里2次)
"we": 1次
Step 2:合并最常见的邻居对
"lo" + "ow" → "low"(出现频率最高!合并!)
Step 3:重复,直到词表大小达标
最终词表里就有"low"这个词了!
→ 高频词保留为整体
→ 低频词被拆成子词组合子词分词 = 词分词和字符分词的"中庸之道"
好处1:词表小
→ 不用给每个完整单词都分配一个编号
好处2:处理新词
→ 遇到"lovable",分成"love"+"able"
→ 认识"love",就猜得出大意
好处3:平衡粒度
→ 不像字符级那么碎(序列太长)
→ 不像词级那么粗(词表太大)假设词表只有3个词:"我", "爱", "电脑"
One-Hot 编码(独热向量):
"我": [1, 0, 0]
"爱": [0, 1, 0]
"电脑": [0, 0, 1]
问题1:每个向量之间都是"正交"的(点积=0)
→ "我"和"爱"在数学上完全没关系!
→ 但实际上"我"经常和"爱"一起出现啊!
问题2:维度爆炸
→ 真实词表有10万个词 → 10万维的向量
→ 绝大多数都是0(稀疏)→ 浪费内存核心思想:"一个词的意义,由它周围出现的词决定" —— J.R. Firth, 1957
"苹果"这个词:
→ 经常和"吃""水果""红色""甜"一起出现
→ 所以"苹果"的向量和"香蕉""梨"很接近
"Apple"(公司):
→ 经常和"iPhone""Mac""发布会"一起出现
→ 所以虽然是同一个词"Apple",在计算机领域语义完全不同
→ 词嵌入能捕捉到这种"语境差异"!词嵌入的可视化(3维简化版):

🔑 一句话记住:词嵌入就是给每个词在高维空间里找个"家"。语义相近的词住得近,反之住得远。
现代模型(GPT 系列、LLaMA、Qwen 等)用的都是 RoPE(Rotary Position Embedding)。
传统方式:PE(pos) + WordEmbedding(word)
→ 位置编码和词向量是**两个独立的向量,简单相加**
问题:
- 加法太弱:位置信息和词义可能打架
- 相对位置要额外算:想知道"我"和"你"差多远,还得专门算RoPE 的思想:不是简单相加,而是用**旋转矩阵**对词向量进行旋转变换
公式:R(d,θ)^m · E
其中:
- R 是旋转矩阵(把向量在空间里"转一下")
- m 是位置(0, 1, 2, ...)
- E 是词向量
旋转之后的效果:
- 词向量还是那个词向量(语义不变)
- 但它的"角度"变了(位置信息被编进去了)
- 相对位置直接由旋转角度差决定!RoPE 的三大优势:
| 优势 | 什么意思 |
|---|---|
| 相对位置天然感知 | 两个token差几位,旋转角度差就体现出来了 |
| 不需要额外向量 | 不像传统PE那样多出一个位置向量 |
| 外推能力强 | 能处理比训练时更长的序列(长上下文必备) |
终于到重头戏了!这一章是 Transformer 最核心的部分,建议读三遍。
"I walked through the door yesterday. The door was red and old."
↑
"door" 指的是哪个?
第一个"door":我昨天穿过的那扇门
第二个"door":是红色的、老旧的那扇(指第一个door)RNN 处理这个问题特别费劲,因为它需要跨越很长的距离才能建立这两个"door"之间的联系——信息传着传着就失真了。
注意力机制的做法:不用传,让模型自己决定看到这个词时,应该重点关注序列里的哪些词。
💡 核心思想:不是固定地传递信息,而是"动态查询"——当前位置想知道什么信息,就去看哪些词最能回答这个问题。
这是全章最重要的概念,没有之一。Attention 机制的核心就是三个向量:Query(查询)、Key(键)、Value(值)。
想象你去图书馆借书:
Query(查询):"我想找一本关于深度学习的书"
→ 这是你的**需求**,你想找什么
Key(键):图书馆给每本书贴的标签 ["深度学习", "机器学习", "历史"]
→ 这是书的**索引标签**,用来匹配你的需求
Value(值):书的实际内容、作者、价格
→ 这是书的**内容**,你真正想要的东西Attention 计算就是:用你的 Query 在所有 Key 中找最匹配的,然后返回对应 Value 的加权组合。
举例:
1. Query="深度学习" → 想要这个
2. 匹配 Key="深度学习"那本书 → 匹配度最高
3. 返回对应 Value → 得到这本书的内容Query(查询):"当前位置的词想问什么?"
词"爱"的 Query = "我需要找什么样的主语?谁来爱我?"
Key(键):"当前位置的词能提供什么信息?"
词"我"的 Key = "我是主语,第一人称代词"
Value(值):"当前位置的词实际上是什么意思?"
词"我"的 Value = "动作的发出者"注意力权重的意思:词"爱"对词"我"的注意力 = 0.8 → "爱"这个词在生成的时候,80% 的信息来自"我"。
下面我们一步步推导 Attention 的完整公式。跟着我的思路走,你会发现它其实非常自然。
输入:X (seq_len × d_model)
seq_len = 序列有多少个词
d_model = 每个词的向量维度
通过三个可学习矩阵生成:
Q = X · W_Q^T ← Query(查询):每个词在问什么
K = X · W_K^T ← Key(键):每个词能提供什么
V = X · W_V^T ← Value(值):每个词的实际内容
W_Q, W_K, W_V 是模型学出来的参数
→ 模型会自动找到最好的"问-答"映射关系scores = Q · K^T ← 得到一个 (seq_len × seq_len) 的矩阵
第 i 行第 j 列:位置 i 的 Query 和位置 j 的 Key 有多像?
scores[i,j] 越大 → 位置 i 越关注位置 j这是个数学 trick,但背后有直观的道理:
如果 Q 和 K 的每个维度都是独立随机变量(均值=0,方差=1):
则 Q·K^T 中每个元素的方差 = d_k
d_k 很大时(比如64维),点积的值会非常大!
举例:
Q = [3, 3, 3, 3](4维,每个元素都是3)
K = [3, 3, 3, 3](4维)
Q·K = 3×3 + 3×3 + 3×3 + 3×3 = 36 ← 方差=36=4×4=d_k×d_k
直接 softmax:
exp(36) / Σexp ≈ 1 ← 这一项概率几乎=1,其他全部≈0
→ 梯度≈0 → 无法训练!❌
缩放:Q·K / √d_k = 36/4 = 9
→ softmax 后梯度正常 → 可以训练!✅💡 为什么叫"Scaled"(缩放):就是给点积结果除以 √d_k,防止它太大导致 softmax 梯度消失。
attention_weights = softmax(scores / √d_k, dim=-1)
让每行的和=1,变成概率分布:
第 i 行 = [α_i0, α_i1, ..., α_in],且 Σα = 1
α_ij 表示:位置 i 分配给位置 j 的注意力比例output = attention_weights · V
第 i 行的输出 = Σⱼ (α_ij × V[j])
= 位置 i 综合所有位置的信息
= 位置 i 的"上下文感知表示"
→ 每个词的输出不仅包含自己的信息
→ 还融合了所有其他词的信息(按注意力权重加权)Attention(Q, K, V) = softmax(QK^T / √d_k) · V就这么简单!四步走:生成QKV → 算相似度 → 缩放 → Softmax → 加权求和。
完整流水线(可视化):

生成 "我爱AI":
Step 1:生成"我"(只能看<BOS>)
Step 2:生成"爱"(只能看<BOS>+"我")
Step 3:生成"AI"(只能看<BOS>+"我"+"爱")推理的时候,模型是一个词一个词生成的,下一个词还没生成出来,自然看不到。
所以训练的时候也要模拟这个过程——当前位置不能看到"未来"的 token。
错误情况(能看到未来):
位置0 (<BOS>) 看到:<BOS> 我 爱 AI <EOS> ← 看到了还没生成的东西!❌
位置1 (我) 看到:<BOS> 我 爱 AI <EOS> ← 偷看了"爱"和"AI"!❌
正确情况(只能看之前):
位置0 (<BOS>) 看到:<BOS>
位置1 (我) 看到:<BOS> 我
位置2 (爱) 看到:<BOS> 我 爱
位置3 (AI) 看到:<BOS> 我 爱 AIimport torch
seq_len = 5
# 创建下三角矩阵:1=可以看,0=要遮住
mask = torch.tril(torch.ones(seq_len, seq_len))
# tensor([[1., 0., 0., 0., 0.],
# [1., 1., 0., 0., 0.],
# [1., 1., 1., 0., 0.],
# [1., 1., 1., 1., 0.],
# [1., 1., 1., 1., 1.]])
# 把要遮住的位置填成 -1e9
scores = scores.masked_fill(mask == 0, -1e9)
# softmax 后,exp(-1e9) 几乎等于 0 → 注意力权重≈0
# → 成功遮住了"未来"的信息句子:"The cat sat on the mat because it was tired"
问题:"it" 指的是谁?
注意力权重(简化版):
The cat sat on the mat because it was tired
"it" 0.0 0.7 0.0 0.0 0.0 0.0 0.0 - 0.0 0.2
→ "it" 把 70% 的注意力给了 "cat"
→ 模型自动学会了 "it = cat"!
→ 另外 20% 给了 "tired"(因为"it was tired"也是个完整短语)🔑 一句话理解 Attention:不是硬性规定"这个词一定指那个词",而是让模型从海量数据里自己学出来"在这种语境下,这两个词大概率相关"。就像你看文章时会自然地"重点关注某些地方"一样。
句子:"The scientist who was born in Germany and won the Nobel prize
later moved to the United States. She made groundbreaking
discoveries in physics."
问题:
- "She" 指的是谁?→ 需要找"Germany"的女性科学家
- "She" 的什么特质被提到?→ 诺贝尔奖,物理学突破
单一注意力可能只能回答"谁"或"什么"中的一个
→ 难以同时捕捉多种关系类比理解:
你读一篇文章,让三个人同时读:
👤 A:语法角度(找主谓宾)
👤 B:情感角度(判断态度)
👤 C:人物关系(理清关系网)
三个人的结论合在一起 → 比任何一个人单独读都全面得多!
多头注意力就是这个道理:
→ 每个头在不同的"子空间"里捕捉不同类型的关系
→ 最后把所有人的发现拼起来Q, K, V ──┬── Head₁: Attention(QW₁^Q, KW₁^K, VW₁^V)
├── Head₂: Attention(QW₂^Q, KW₂^K, VW₂^V)
├── ...
└── Headₕ: Attention(QWₕ^Q, KWₕ^K, VWₕ^V)
│
▼
Concat(把所有头拼起来)
│
▼
Linear(再做一个线性变换)
│
▼
输出设定:
- d_model = 512
- h = 8 个头
- 每头维度:d_k = d_v = 64(512/8)
单头参数:W_Q, W_K, W_V 各 = 512×512
总计:3 × 512 × 512 = 786,432
多头(8头)参数:
- 每个头:64×512 × 3 = 98,304
- 8个头:8 × 98,304 = 786,304
- Concat后线性层:512×512 = 262,144
总计:1,048,448 ≈ 单头的1.3倍
→ 参数量基本不变!
→ 只是把原来一个大矩阵的操作,拆成了8个小矩阵并行做通过可视化注意力权重,研究者发现不同头倾向于关注不同关系:
| 头的类型 | 关注什么 | 例子 |
|---|---|---|
| 句法头 | 语法结构 | 主语↔谓语、修饰词↔被修饰词 |
| 指代头 | 代词指向 | "it"高度关注"cat" |
| 语义头 | 同义关系 | "happy"↔"joyful" |
| 位置头 | 邻近词 | 相邻词之间注意力更高 |
💡 为什么多头有效:因为不同的头负责不同的"职责",就像一个团队里有不同工种的专家,大家各司其职,最后汇总判断。
层数一深,梯度就会出现两种极端情况:
假设每层梯度传递效率是0.8:
第5层:梯度 = 0.8⁵ ≈ 0.33 ← 还能传点
第50层:梯度 = 0.8⁵⁰ ≈ 0.00001 ← 梯度消失了!❌
假设每层梯度是1.2(稍微大一点):
第50层:梯度 = 1.2⁵⁰ ≈ 9100 ← 梯度爆炸!❌
这就是为什么之前深层网络很难训练的原因残差连接(来自 ResNet 的天才想法):
传统网络:
x ──► [Layer] ──► F(x)
残差连接:
x ──┬──► [Layer] ──► F(x) ──┐
│ │
└───── + ←─ 恒等映射 ────┘
│
▼
y = F(x) + x关键点:多了一条恒等映射(x 直接传过去)的支路!
梯度流动:
∂L/∂x = ∂L/∂y · (∂F/∂x + 1)
= ∂L/∂y · ∂F/∂x + ∂L/∂y
即使 ∂F/∂x → 0(梯度消失)→ 还有 ∂L/∂y 在!(残差通路)
即使 ∂F/∂x → 很大(梯度爆炸)→ 还有一个 +1 在!(拉回来)
→ 残差连接 = 梯度的"定海神针"!
→ 不管主路多难走,恒等映射永远保证梯度能传回去每个子层都加残差连接:
y = LayerNorm(x + Sublayer(x))
其中 Sublayer 可以是:
- Multi-Head Attention
- Feed-Forward Network
即使 Sublayer 学不到东西(F(x)≈0):
→ 输出 y = LayerNorm(x + 0) = LayerNorm(x) ≈ x
→ 变成恒等映射!模型不会变差
→ 这就是为什么 Transformer 可以堆几十层、上百层的原因!Batch Norm(批归一化):
- 对 batch 方向(横向)做归一化
- 需要较大的 batch size
- 适合图像,不适合变长序列的 NLP
Layer Norm(层归一化):
- 对单个样本的特征方向(纵向)做归一化
- 不依赖 batch size
- 天生适合 NLP(每个序列长度不同)LayerNorm(x) = γ · (x - μ) / √(σ² + ε) + β
其中:
- μ = mean(x) ← 沿特征维度求均值
- σ² = var(x) ← 沿特征维度求方差
- γ, β = 可学习参数 ← 允许模型自己学最优的缩放和偏移
- ε = 1e-8 ← 防止除以0💡 为什么要归一化:想象你做饭,每次食材的口味都完全随机(均值方差不稳定),你根本没法掌握火候。归一化就是让每层的输入"口味稳定"(均值=0,方差=1),这样模型才好学习。
Attention(Q, K, V) = softmax(QK^T / √d) · V
无论"我爱你"还是"你爱我":
"我爱你" → [v_我, v_爱, v_你]
"你爱我" → [v_你, v_爱, v_我]
→ 交换词的位置,输出向量也跟着交换
→ 但 v_爱 在两个句子中是完全相同的!
→ Attention 只关心"词是什么",不关心"词在哪"
→ 所以"I love you"和"You love I"在Attention眼里可能是一样的!❌Transformer 原文用的是正弦/余弦位置编码:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
其中:
- pos: 位置(0, 1, 2, ...)
- i: 维度索引
- d_model: 总维度关键性质:相对位置可以通过线性变换得到
sin(θ + δ) = sin(θ)cos(δ) + cos(θ)sin(δ)
cos(θ + δ) = cos(θ)cos(δ) - sin(θ)sin(δ)
→ 相对位置 δ 被编码在 cos(δ) 和 sin(δ) 里
→ 任意两个位置的相对关系都可以"推导"出来RoPE 是目前最主流的位置编码方案,被 GPT、LLaMA、Qwen 等广泛采用。
RoPE 核心公式:
f_q(x_m, m) = R_Θ(m) · q
f_k(x_n, n) = R_Θ(n) · k
旋转矩阵 R_Θ(m):
[cos(mθ) -sin(mθ)]
[sin(mθ) cos(mθ)]
关键性质(内积不变性):
⟨f_q, f_k⟩ = ⟨q, k⟩ · cos((m-n)θ)
→ Attention score 只取决于相对位置 (m-n)!
→ 天然具有相对位置感知能力| 特性 | 原始 PE | RoPE |
|---|---|---|
| 编码方式 | 加法(PE + Embedding) | 旋转乘法 |
| 相对位置 | 需要额外计算 | 内置、天然 |
| 适用模型 | BERT(Encoder) | GPT, LLaMA, Qwen(Decoder) |
| 外推能力 | 较差 | 优秀 |
输入序列
│
▼
[Token Embedding + Positional Encoding] ← 词向量 + 位置信息
│
▼
┌──────────────────────────────────────────┐
│ Encoder Layer (×N) │
│ │
│ Multi-Head Self-Attention │
│ (Q=K=V=输入,每个位置都能看到所有位置) │
│ │ │
│ ▼ │
│ Add & Layer Norm │
│ │ │
│ ▼ │
│ Feed-Forward Network │
│ Linear → ReLU/GELU → Linear │
│ (对每个位置独立做非线性变换) │
│ │ │
│ ▼ │
│ Add & Layer Norm │
└──────────────────────────────────────────┘
│
▼
输出序列(与输入等长)FFN(x) = Linear(ReLU(Linear(x)))
- 内层维度 = 4 × d_model(如 512 → 2048 → 512)
- 激活函数 = ReLU 或 GELU(更现代)
Attention 和 FFN 的分工:
- Attention:捕获 token 之间的关系(看全局)
- FFN:消化和变换每个 token 的信息(消化知识)
⚠️ FFN 占了 Transformer 约 2/3 的参数!
→ 显存占用的大头主要在 FFN,不是在 Attention输出序列(训练时:<BOS> 我 爱 AI <EOS>)
│
▼
[Token Embedding + Positional Encoding]
│
▼
┌──────────────────────────────────────────┐
│ Decoder Layer (×N) │
│ │
│ Masked Multi-Head Self-Attention │
│ (看不到"未来"的词,只能看自己和之前的) │
│ │ │
│ ▼ │
│ Add & Layer Norm │
│ │ │
│ ▼ │
│ Cross-Attention │
│ (Q来自解码器,K=V来自编码器) │
│ 解码器"查询"编码器的信息 │
│ │ │
│ ▼ │
│ Add & Layer Norm │
│ │ │
│ ▼ │
│ Feed-Forward Network │
│ │ │
│ ▼ │
│ Add & Layer Norm │
└──────────────────────────────────────────┘
│
▼
Output logits → softmax → 预测下一个 token翻译例子:编码器输入 "I love AI",解码器输出 "我爱AI"
解码器 "爱" 字的 Cross-Attention 分析:
- 解码器 "爱" 字的 Query
→ 和编码器所有位置的 Key 计算相似度
→ 发现 "love" 的 Key 最匹配
→ 提取 "love" 的 Value
→ 生成 "爱" 时大量参考了 "love" 的信息
→ Cross-Attention = 解码器和编码器之间的"翻译桥"
→ 没有它,解码器就不知道编码器说了什么| 维度 | RNN/LSTM | Transformer |
|---|---|---|
| 信息传递 | 顺序传递,路径很长 | 并行传递,路径=O(1) |
| 长期依赖 | 很难捕获,梯度消失 | 直接建模,不费力 |
| 并行计算 | 不能并行(时序依赖) | 完全并行(GPU最爱) |
| 计算复杂度 | O(n × d),线性 | O(n² × d),平方 |
| 位置感知 | 天生具有 | 需要额外位置编码 |
| 显存占用 | O(n),线性增长 | O(n²),平方增长 |
选 Transformer ✅:
✅ 序列长度中等偏长(> 100 tokens)
✅ 需要捕获长距离依赖
✅ GPU 算力充足
✅ 并行训练效率重要
选 RNN/LSTM ✅:
✅ 超长序列(> 10,000 tokens)+ 流式处理
✅ 实时性要求高(增量预测,不需要看完再答)
✅ 硬件资源极度受限(显存不够用)
✅ 真正的"时间序列"(股票、语音、视频帧)当 n >> d 时(长序列场景):
O(n² × d) 主导 → 序列长度的平方成为瓶颈
举例:
- n = 1000, d = 512:Attention 计算量 ≈ 512,000,000
- n = 10000, d = 512:Attention 计算量 ≈ 51,200,000,000
→ 序列长度增长10倍,计算量增长100倍!
→ 这就是为什么"长上下文"是个昂贵的奢侈Transformer 显存都被谁吃了?
1. 模型参数
- Embedding: n_vocab × d_model
- Attention: 4 × d_model²(Q/K/V/O投影)
- FFN: 2 × d_model × d_ff
2. 激活值(和序列长度成正比)
- Q/K/V: n × d_k × h
- 注意力权重矩阵: n × n(这是大头!)
- FFN 中间激活
3. 优化器状态(训练时才需要)
- Adam: 2 × 参数数量
GPT-2 medium(345M参数)示例:
- 参数量:~350 MB
- 全精度训练:~2.8 GB
- 混合精度训练:~1.5 GB(省了快一半!)三大优化方向:
1. Flash Attention(目前最流行)
├── 思想:分块计算,显存从O(n²)降到O(n)
├── 效果:显存减少2-4倍,速度提升2-3倍
└── 实现:CUDA kernel 融合(底层硬优化)
2. Sparse Attention(稀疏注意力)
├── 思想:只算部分位置的注意力(不是全连接)
├── 代表:Longformer, BigBird
└── 效果:O(n√n) 或 O(n log n)
3. Linear Attention(线性注意力)
├── 思想:改变数学形式,绕过 softmax
├── 效果:O(n × d²),和序列长度无关
└── 代表:Performer, Mamba┌──────────────────────────────────────────────────────────┐
│ │
│ ① 分词与嵌入 → 把文字变成计算机能算的向量 │
│ ② Self-Attention → 让每个位置直接看到所有其他位置 │
│ ③ Multi-Head → 多角度并行理解 │
│ ④ 位置编码 → 给序列装上"顺序感" │
│ ⑤ 残差连接 → 保障深层网络的梯度流动 │
│ ⑥ 层归一化 → 稳定每层的输入分布 │
│ ⑦ FFN → 每个位置独立的非线性变换 │
│ ⑧ Cross-Attention→ 解码器"查询"编码器信息 │
│ │
└──────────────────────────────────────────────────────────┘"I love AI" 的处理流程:
Step 1: Tokenize → [1028, 4512, 28901] ← 词表索引
Step 2: Embed → [[v₁], [v₂], [v₃]] ← 稠密向量
Step 3: + Positional Encoding ← 加上位置信息
Step 4: Self-Attention (×N层) ← 捕获词间关系
Step 5: FFN (×N层) ← 消化和变换
Step 6: Output logits ← 预测下一个词| 题目 | 考什么 | 难度 |
|---|---|---|
| Q1: 解释 Q/K/V 三个向量的含义,用机器翻译举例 | 理解力 | ⭐⭐ |
| Q2: Attention 为什么要除以 √d_k?不除会怎样? | 分析力 | ⭐⭐⭐ |
| Q3: Teacher Forcing 为什么只在训练用,推理为什么不行? | 分析力 | ⭐⭐⭐ |
| Q4: Layer Normalization 和 Batch Normalization 适用场景有何不同? | 评价力 | ⭐⭐⭐ |
| Q5: 序列长度10000,d=512,batch=1,16位精度,估算Self-Attention显存占用 | 综合应用 | ⭐⭐⭐⭐ |
| Q6: RoPE 为什么能天然实现相对位置编码?比原始PE好在哪? | 深度理解 | ⭐⭐⭐⭐ |
| 资料 | 推荐理由 |
|---|---|
| Attention Is All You Need arXiv:1706.03762 | 必读原文,祖师爷论文 |
| The Illustrated Transformer (Jay Alammar) | 动图讲解,入门神器 |
| Layer Normalization arXiv:1607.06450 | LN 原始论文 |
| 苏剑林 RoPE 论文 | 国内最详尽的 RoPE 分析 |
| FlashAttention-2 arXiv:2307.08691 | 当前最优注意力实现 |
| Adam arXiv:1412.6980 | 优化器理论基础 |