在 RoPE 中:
- 每个 token 位置(pos) 都会被映射成一个 d 维的位置编码向量
- 这个向量的每一维都对应一个不同的旋转频率
- 维度索引 i 越小 → 对应的频率越高 → 变化越快
- 维度索引 i 越大 → 对应的频率越低 → 变化越慢
所以:
👉 所有位置(无论是 pos=1、pos=100 还是 pos=10000)都会使用从高到低的所有频率维度。
🧮 数学回顾:RoPE 的角度是怎么算的?
对于第 iii 个维度(通常是偶数维),其对应的角频率为:
θi=1100002i/d \theta_i = \frac{1}{10000^{2i/d}} θi=100002i/d1
然后该维度上的旋转角度为:
θi×pos \theta_i \times \text{pos} θi×pos
比如:
- 当 i=0i=0i=0(第一个维度):θ0=1\theta_0 = 1θ0=1 → 角度 = 1×pos1 \times \text{pos}1×pos
- 当 i=64i=64i=64(假设 d=128):θ64≈1/100001=10−4\theta_{64} \approx 1/10000^{1} = 10^{-4}θ64≈1/100001=10−4 → 角度 = 0.0001×pos0.0001 \times \text{pos}0.0001×pos
所以:
- 对于 pos=100:
- 第 0 维的角度 = 100100100
- 第 64 维的角度 = 0.010.010.01
➡️ 前面维度(低 i)变化剧烈(高频),后面维度(高 i)变化缓慢(低频)
📊 举个具体例子对比:pos=1 vs pos=2 vs pos=1000
我们来看两个维度的情况:
✅ 高频维度(i=0, θ0=1\theta_0 = 1θ0=1)
| pos | 角度 θ⋅pos\theta \cdot posθ⋅pos | cos(角度) |
|---|---|---|
| 1 | 1 rad ≈ 57° | ~0.54 |
| 2 | 2 rad ≈ 114° | ~-0.42 |
| 3 | 3 rad ≈ 172° | ~-0.99 |
➡️ 相邻位置之间变化非常大 → 能很好地区分 pos=1 和 pos=2
✅ 这就是“高频编码短距离差异”
✅ 低频维度(i=64, θ64=10−4\theta_{64} = 10^{-4}θ64=10−4)
| pos | 角度 θ⋅pos\theta \cdot posθ⋅pos | cos(角度) |
|---|---|---|
| 1 | 0.0001 rad | ~1.0000 |
| 2 | 0.0002 rad | ~1.0000 |
| 1000 | 0.1 rad ≈ 5.7° | ~0.9950 |
| 10000 | 1 rad ≈ 57° | ~0.5400 |
➡️ 即使 pos 从 1 到 1000,cos 几乎没变;只有到了很远才开始变化
✅ 这就是“低频编码长距离趋势”——它不关心你是第 1 个还是第 2 个,但它知道:
- 如果角度还是接近 1 → “我在开头”
- 如果角度变成 0.5 → “我已经走到一万了”
📊 可视化
下面是一段 Python 可视化代码,它会画出 RoPE 中不同维度(不同频率)的 cos(θ × pos) 随位置 pos 变化的曲线,让你直观看到:
- 前面的维度(低 i):高频 → 波动剧烈 ✅ 编码“短距离差异”
- 后面的维度(高 i):低频 → 变化缓慢 ✅ 编码“长距离趋势”
import numpy as np
import matplotlib.pyplot as plt
# 模型配置参数
d = 128 # 隐藏层维度(如 Qwen3 的 d_model)
base = 10000 # RoPE 的 base(标准设置)
max_pos = 512 # 最大可视化位置范围
# 生成所有维度的 θ_i = 1 / (base^(2i/d))
i = np.arange(0, d // 2) # i 从 0 到 63(共64个频率)
theta = 1.0 / (base ** (2 * i / d)) # 形状: [64,]
# 生成位置序列
positions = np.arange(0, max_pos) # pos: 0 ~ 511
# 计算每个维度在每个位置上的角度:θ_i * pos
# 结果 shape: [num_dims, num_positions] = [64, 512]
angles = np.outer(theta, positions) # 等价于 theta[:, None] * positions[None, :]
# 计算 cos 编码(用于 RoPE 的一部分)
cos_encodings = np.cos(angles)
# === 开始绘图 ===
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
axes = axes.flatten()
# 想要看哪几个维度?我们选前、中、后各两个代表高频、中频、低频
dim_indices = [0, 1, 31, 32, 62, 63] # 前、中、后段各选两个
titles = [
f"Dim {i[0]} (very high freq)",
f"Dim {i[1]} (high freq)",
f"Dim {i[31]} (mid-high freq)",
f"Dim {i[32]} (mid-low freq)",
f"Dim {i[62]} (low freq)",
f"Dim {i[63]} (very low freq)"
]
for idx, dim in enumerate(dim_indices):
ax = axes[idx]
ax.plot(positions, cos_encodings[dim], lw=2, label=f'cos(θ_{dim} × pos)')
ax.set_title(titles[idx])
ax.set_xlabel("Position")
ax.set_ylabel("Value")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.suptitle(f"RoPE Position Encoding Across Dimensions (d={d}, base={base})",
fontsize=16, y=1.02)
plt.show()
输出图像说明
- 运行这段代码后你会看到 6 个子图,分别展示不同维度的 cos(θ×pos) 曲线:
dim=0,1:波动极快 → 很小的位置变化就导致值大幅跳变 → 区分相邻 token
dim=31,32:中等波动 → 在几十到上百位置内有变化 → 捕捉局部结构
dim=62,63:几乎是一条平缓下降曲线 → 要到几百甚至上千才明显变 → 表示“我在远端”

✅ 总结:
✔️ “高频编码短距离差异” 是说:位置编码向量的前半部分(低 i 维度)随位置快速变化,能敏锐感知相邻 token 的差别
✔️ “低频编码长距离趋势” 是说:后半部分(高 i 维度)随位置缓慢变化,帮助模型感知‘我现在是在开头、中间还是结尾’这种全局信息

2961

被折叠的 条评论
为什么被折叠?



