Skip to content

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️⃣RoPELlama, MiniMind高效+可外推-

MiniMind 的选择:RoPE(Rotary Position Embedding)


📐 2. 是什么(What)

RoPE 核心思想

一句话总结:用旋转角度编码位置,通过旋转向量来表示不同位置。

基本原理

位置 0 → 旋转 0°
位置 1 → 旋转 θ°
位置 2 → 旋转 2θ°
位置 3 → 旋转 3θ°
...
位置 m → 旋转 mθ°

数学定义

对于向量 x=[x0,x1,...,xd1],位置 m 的 RoPE 变换:

RoPE(x,m)=[cos(mθ0)sin(mθ0)sin(mθ0)cos(mθ0)cos(mθ1)sin(mθ1)sin(mθ1)cos(mθ1)][x0x1x2x3]

关键点

  • 每两个维度配对,应用一个旋转矩阵
  • 不同维度对使用不同的频率 θi

频率计算

θi=1base2i/d

其中:

  • base=10000(标准)或 1000000(MiniMind)
  • d:头维度(head_dim)
  • i=0,1,2,...,d/21

MiniMind 的参数(head_dim=64):

python
rope_base = 1000000.0  # 更大的base → 更好的长度外推能力
freqs = [1/1000000^(2i/64) for i in range(32)]

生成 32 个不同频率:

  • 高频(i=0):θ0=1.0,每 2π 转一圈(约 6 个 token)
  • 中频(i=15):θ15=0.001,每 6283 个 token 转一圈
  • 低频(i=31):θ31=0.000001,每 628万 个 token 转一圈

为什么需要多频率?

🕰️ 钟表类比

指针旋转速度作用对应 RoPE
秒针快(1分钟一圈)精确到秒高频(局部位置)
分针中(1小时一圈)精确到分中频(中等距离)
时针慢(12小时一圈)精确到时低频(全局位置)

单频率的问题

  • 只用高频:长序列时"转了很多圈",位置信息模糊
  • 只用低频:短序列时"转得太慢",位置区分度不够

多频率的优势

  • 高频:区分相邻 token(如词内的字)
  • 低频:区分远距离 token(如段落之间)
  • 组合起来:可以唯一标识百万级位置

相对位置信息的自然产生

关键性质:RoPE 编码后的点积只依赖相对位置!

数学证明(简化):

位置 m 的 Query:qm=RoPE(q,m) 位置 n 的 Key:kn=RoPE(k,n)

点积:

qmkn=f(q,k,mn)

只依赖 相对距离 mn,不依赖绝对位置 mn

实际效果

  • 位置 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. 关键要点总结

核心结论

  1. 为什么需要位置编码

    • Attention 本身是排列不变的
    • 需要额外信息来区分顺序
  2. 为什么选择 RoPE

    • 自然包含相对位置信息
    • 计算高效(直接旋转)
    • 支持长度外推
  3. 为什么使用多频率

    • 高频:区分局部位置
    • 低频:区分全局位置
    • 组合:唯一标识任意位置

设计原则

在 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. 延伸阅读

必读论文

推荐博客

代码实现

  • MiniMind: model/model_minimind.py:108-200 - RoPE 实现
  • MiniMind: model/model_minimind.py:250-330 - Attention 中的使用

自测题

  • 📝 quiz.md - 完成自测题巩固理解

🎯 完成检查清单

学完本文档后,检查你是否能够:

  • [ ] 用自己的话解释 Attention 的排列不变性
  • [ ] 说出 RoPE 的数学表达式
  • [ ] 解释为什么需要多个频率
  • [ ] 解释 RoPE 如何产生相对位置信息
  • [ ] 画出 RoPE 的旋转示意图
  • [ ] 从零实现一个简单的 RoPE

如果还有不清楚的地方,回到实验代码,动手验证!

基于 MiniMind 项目的学习笔记