Position Encoding(位置编码)教学文档
理解 Transformer 如何处理位置信息,以及 RoPE 的工作原理
🤔 1. 为什么(Why)
问题场景:Attention 的排列不变性
问题:Self-Attention 机制本身是排列不变的(permutation invariant)
例子:
句子1: "我 喜欢 你" → {我, 喜欢, 你}
句子2: "你 喜欢 我" → {你, 喜欢, 我}如果没有位置编码,Attention 计算时:
- 两个句子包含相同的词(集合相同)
- Attention 权重完全相同
- 无法区分顺序!
但显然,两个句子意思完全不同!
直觉理解:给词"打标签"
🏷️ 类比:位置编码就像给每个词贴上"座位号"
没有座位号:
- 老师点名:"张三、李四、王五"
- 学生随便坐
- 每天座位都不一样,无法记住位置关系
有座位号:
- 张三 → 座位1
- 李四 → 座位2
- 王五 → 座位3
- 位置关系固定,容易记忆
位置编码的演进
| 代 | 方案 | 使用模型 | 优点 | 缺点 |
|---|---|---|---|---|
| 1️⃣ | 绝对位置编码 | BERT, GPT-2 | 简单 | 无法外推到更长序列 |
| 2️⃣ | 相对位置编码 | T5, XLNet | 更灵活 | 计算复杂,难优化 |
| 3️⃣ | RoPE | Llama, MiniMind | 高效+可外推 | - |
MiniMind 的选择:RoPE(Rotary Position Embedding)
📐 2. 是什么(What)
RoPE 核心思想
一句话总结:用旋转角度编码位置,通过旋转向量来表示不同位置。
基本原理:
位置 0 → 旋转 0°
位置 1 → 旋转 θ°
位置 2 → 旋转 2θ°
位置 3 → 旋转 3θ°
...
位置 m → 旋转 mθ°数学定义
对于向量
关键点:
- 每两个维度配对,应用一个旋转矩阵
- 不同维度对使用不同的频率
频率计算
其中:
(标准)或 (MiniMind) :头维度(head_dim)
MiniMind 的参数(head_dim=64):
python
rope_base = 1000000.0 # 更大的base → 更好的长度外推能力
freqs = [1/1000000^(2i/64) for i in range(32)]生成 32 个不同频率:
- 高频(i=0):
,每 转一圈(约 6 个 token) - 中频(i=15):
,每 6283 个 token 转一圈 - 低频(i=31):
,每 628万 个 token 转一圈
为什么需要多频率?
🕰️ 钟表类比:
| 指针 | 旋转速度 | 作用 | 对应 RoPE |
|---|---|---|---|
| 秒针 | 快(1分钟一圈) | 精确到秒 | 高频(局部位置) |
| 分针 | 中(1小时一圈) | 精确到分 | 中频(中等距离) |
| 时针 | 慢(12小时一圈) | 精确到时 | 低频(全局位置) |
单频率的问题:
- 只用高频:长序列时"转了很多圈",位置信息模糊
- 只用低频:短序列时"转得太慢",位置区分度不够
多频率的优势:
- 高频:区分相邻 token(如词内的字)
- 低频:区分远距离 token(如段落之间)
- 组合起来:可以唯一标识百万级位置
相对位置信息的自然产生
关键性质:RoPE 编码后的点积只依赖相对位置!
数学证明(简化):
位置
点积:
只依赖 相对距离
实际效果:
- 位置 5 和位置 8 之间的关系 = 位置 100 和位置 103 之间的关系
- 模型学到的是"相隔 3 个位置"的模式,而非具体位置
长度外推能力(YaRN)
问题:训练时序列长度 256,推理时能处理 512 吗?
RoPE 的优势:
- 绝对位置编码:❌ 无法外推(未见过的位置没有编码)
- RoPE:✅ 可以外推(只是旋转更多角度)
YaRN 算法(MiniMind 支持):
- 动态调整旋转频率
- 让模型更好地适应超长序列
- 配置:
inference_rope_scaling=True
🔬 3. 怎么验证(How to Verify)
实验 1:证明排列不变性
目的:证明没有位置编码时,Attention 无法区分顺序
方法:
- 输入两个序列:[A, B, C] 和 [C, B, A]
- 计算 Attention 输出
- 对比是否相同
运行:
bash
python experiments/exp1_why_position.py预期结果:
- 无位置编码:两个序列的输出完全相同
- 有 RoPE:两个序列的输出不同
实验 2:RoPE 基础原理
目的:可视化 RoPE 如何旋转向量
方法:
- 在 2D 平面上展示旋转效果
- 不同位置的向量旋转不同角度
运行:
bash
python experiments/exp2_rope_basics.py预期输出:
- 旋转动画或静态图
- 展示位置 0, 1, 2, 3 的向量方向
实验 3:多频率机制
目的:展示不同频率如何组合编码位置
方法:
- 绘制不同频率的旋转曲线
- 展示组合后的唯一性
运行:
bash
python experiments/exp3_multi_frequency.py预期输出:
- 多条正弦曲线(不同频率)
- 组合后的复杂模式
实验 4:完整实现(可选)
目的:理解 MiniMind 的真实实现
方法:
- 完整的 RoPE 实现代码
- 包含 YaRN 长度外推
运行:
bash
python experiments/exp4_rope_explained.py💡 4. 关键要点总结
核心结论
为什么需要位置编码:
- Attention 本身是排列不变的
- 需要额外信息来区分顺序
为什么选择 RoPE:
- 自然包含相对位置信息
- 计算高效(直接旋转)
- 支持长度外推
为什么使用多频率:
- 高频:区分局部位置
- 低频:区分全局位置
- 组合:唯一标识任意位置
设计原则
在 MiniMind 中,RoPE 应用在 Attention 的 Q 和 K 上:
python
def forward(self, x, pos_ids):
# 计算 Q, K, V
q, k, v = self.split_heads(x)
# 应用 RoPE(只对 Q 和 K)
q = apply_rotary_emb(q, freqs_cis[pos_ids])
k = apply_rotary_emb(k, freqs_cis[pos_ids])
# 计算 Attention(V 不需要位置编码)
attn = softmax(q @ k.T / sqrt(d))
output = attn @ v
return output记住:RoPE 只应用于 Q 和 K,不应用于 V!
📚 5. 延伸阅读
必读论文
- RoFormer: Enhanced Transformer with Rotary Position Embedding (2021)
- 提出 RoPE,证明相对位置性质
推荐博客
代码实现
- MiniMind:
model/model_minimind.py:108-200- RoPE 实现 - MiniMind:
model/model_minimind.py:250-330- Attention 中的使用
自测题
- 📝 quiz.md - 完成自测题巩固理解
🎯 完成检查清单
学完本文档后,检查你是否能够:
- [ ] 用自己的话解释 Attention 的排列不变性
- [ ] 说出 RoPE 的数学表达式
- [ ] 解释为什么需要多个频率
- [ ] 解释 RoPE 如何产生相对位置信息
- [ ] 画出 RoPE 的旋转示意图
- [ ] 从零实现一个简单的 RoPE
如果还有不清楚的地方,回到实验代码,动手验证!