在 Transformer 中,位置编码位于输入处理数据的最初阶段,也就是在嵌入层之后,在 Encoder/Decoder 之前
为什么要有位置编码?
在将序列传入给 Encoder/Decoder 之前,序列要包含位置信息和语义信息。语义信息在 Word Embeddings 阶段就已经赋予了,所以在将序列给到 Encoder/Decoder 之前,还有给序列赋予位置信息。这一步骤是必不可少的,如果没有位置编码,那 Token 序列在给到 Encoder/Decoder 之后,他可能理解不了原始序列想表达的是什么意思,举个例子
如果不加位置编码,上图中这些句子会被模型视为相同输入,而且 Attention 本质上是置换等变的,即如果打乱输入序列的顺序,输出序列会以完全相同的方式被一同打乱
通过 Attention 的计算公式里就可以直接看出,他对元素在序列中的相对或绝对位置是并不关心的
因此,位置编码是十分重要的,需要给模型一个关于顺序的 “提示”
位置编码的公式
Transformer 论文中,采用的是正余弦位置编码,核心是利用不同频率的正弦/余弦函数为每一个位置生成一个独特的向量。对于每对相邻维度,偶数维度 2i 使用正弦函数,奇数维度 2i+1 使用余弦函数
- pos 是 token 在序列中的绝对位置索引(从 0 开始)
- i 代表是向量维度索引(不是维度),取值范围为 0 <= i < d model / 2
- d model 是模型的嵌入维度,即词向量维度,也就是隐藏层维度(如 512)
- PE(pos, j) 表示位置 pos 的编码向量中第 j 维的值
这种设计让低维度(小 i)对应高频变化,高纬度(大 i)对应低频变化,形成类似“时间信号”的分层频率结构。
当 i = 0 时,10000^0 = 1,波长为 2Π,波长短,函数值变化快,有助于捕捉元素之间近距离的关系,具备高敏感性,当模型在处理序列时,看到两个词在低纬度位置编码上差异巨大,就可以立刻推断出,这两个词非常接近;
当 i = d model/2 - 1 时,10000^(d model - 2)/d model ≈ 10000,波长为 10000 * 2Π,在很长的序列范围内,高纬度的编码值几乎恒定,有助于学习长距离依赖关系。提供了一个稳定的、粗粒度的“区域”或“上下文”信号,在学习非常长的序列时,如果发现两个词在高维的位置编码上非常相似,就可以猜测,这两个词在同一个句子里;
至于为什么要选择 10000 作为基数,是因为这样可以让位置编码的波长依据维度形成 2Π ~ 10000 * 2Π 的几何级数,保证位置编码的频率覆盖范围足够大,同时兼顾长序列与短序列
为什么不采用 0,1,2 ... 这种整数编码?
整数编码直接将索引位置作为特征(如位置 3 的向量为 [0, 0, 1, 0, ... 0]),虽然方便理解,但是存在三个致命缺陷:
- 语义无关性:整数间的数值差(如 5 - 2 = 3)并不对应语义距离,模型无法学习“近邻词关联更强”的规律,比如 “猫坐在沙发上”,“坐” 与 “猫”、“沙发上” 的联系比 “猫” 和 “沙发上” 的直接关联更紧密,若无法学习这个规律,就会像不懂语法的初学者,可能错误理解 “我刚刚在超市买冰红茶” 中 “买” 的对象是 “超市” 而非 “冰红茶”
- 外推失效:训练时未见过的位置(比如训练时超过 512)无对应编码,无法处理超长文本。比如训练时最大的序列是 1024,但是在推理时,如果遇到 1025 可能就不认识了(不同的位置编码对外推的处理能力差异极大,这也是现在 RoPE 等现代方法被广泛采用的核心原因)
- 数值不稳定:大整数可能会导致梯度爆炸,且无法通过矩阵正交性保证向量空间的结构化
为什么不采用可学习位置编码?
通过随机初始化一个 max_seq_len * d model 的矩阵,在训练中学习位置特征。优点时实现简单,但外推性极差,若推理时序列长度超过训练时的 max_seq_len,超出部分的位置编码无定义,需截断或填充,导致性能骤降。而且这样做,换可能导致训练不稳定,如果有问题还需要专门调优这个网络架构,有点儿本末倒置了,毕竟 Self-Attention 就已经够复杂的了
使用正余弦位置编码具备以下几个特性
- 唯一性与有界性:各位置的编码向量互不相同;所有的值都在 [-1, 1] 范围内,避免出现大数值,导致梯度爆炸的发生;而且不需要学习,是确定性的;显示的引入了位置信息,明确的告诉模型,第几个词在哪里
- 无限外推能力:能够让模型外推到训练时未见过的序列长度。这个特性取决于正余弦函数的连续性和无限定义域,即函数 sinx 和 cosx 对于任何实数 x 都是有定义的,可以对任意位置索引生成编码,不受训练数据长度的限制,而且编码模式沿着序列位置平滑变化,没有突变点
- 能保持相对位置关系的一致性:这一特性是通过数学特性来保证的,即任意固定位置偏移的相对位置关系可以被表达为线性变换
也就是说,偏移之后的位置编码可以通过原位置的位置编码的线性组合得到,而且这个线性系数,仅仅和偏移量 k 相关,是独立于位置量的,这样就可以让模型非常方便的从绝对位置中去推断相对关系,但是编码的仍然是每个 Token 的绝对位置
但是在实际中,由于位置编码与词向量直接相加,相对位置关系仍需要模型通过 Attention 间接的去学习,效率比较低,那有没有什么方法可以直接编码相对位置关系的方式,这就说到了 RoPE(旋转位置编码)
为什么非要学习相对位置关系?
因为这种方式更符合语言中 “语义依赖相对位置” 的特性,比如 “北京” 和 “中国” 的关联与绝对位置无关
旋转位置编码(RoPE)
它不像正余弦位置编码那样,给每个 Token 添加一个位置向量,而是在注意力机制计算中对 Q 和 K 向量做位置相关的二维旋转
首先先把向量的维度两两配对,看作一系列二维平面。给定一个绝对位置 p,对于第 i 对维度(2i, 2i + 1),定义一个旋转角,这个旋转角 θ 和向量在序列中的绝对位置有关:
base 是控制频率范围的超参数;d head 代表每个注意力头的维度,即 d head = d model/头数,若 d head = 64,此时 θ 的计算会基于 64 维特征空间进行角度划分,使得相邻维度的旋转角度呈指数级递减,既保证位置编码的周期性,又避免高频维度的数值震荡,这一设计让旋转位置编码能自适应不同模型规模,同时保持相对位置信息的平移不变性
当 Q 和 K 都经过 RoPE 变换后,他们的注意力计算如下:
其中最关键的内积计算变为:
即 Q,K 和相对位置 m - n 相关的函数,绝对位置的信息消失了,这样就可以使得相对距离绝对注意力,即两个 token 之间的注意力分数只依赖于他们的相对位置差,而不是绝对位置。
而且通过相对位置 m - n,还可以有以下这些特性:
- 平移不变性:序列整体平移不会改变内部的注意力模式
- 保留长度外推能力:由于旋转的角度没有限制,它是连续变化的,理论上还是可以外推到任意的程度,超出训练长度的位置,仍然可以保持相对位置的一致性,随着相对距离增加,注意力分数自然衰减
如果想要详细了解上面的旋转角计算公式,可以看这篇文章 (12 封私信 / 10 条消息) 一文看懂 LLaMA 中的旋转式位置编码(Rotary Position Embedding) - 知乎
总之呢,RoPE 通过复数旋转矩阵,将绝对位置编码转换成相对位置表征,将位置 m 的词向量施加旋转 mθ,位置 n 的词向量旋转 nθ,两者点积结果仅取决于相对距离 m - n。而且采用 RoPE 就没有了与词向量相加的步骤,它通过乘法旋转而非加法去注入位置信息,这个是其与传统的正余弦位置编码的核心区别。具体的步骤总结,如下图所示
为什么要舍弃加法?
- 相对位置自洽性:旋转后 Q 与 K 的点积 q'm * k'n 仅依赖相对位置 m - n ,而非绝对位置,这解决了传统加法编码中长序列位置混淆的问题
- 维度利用率:加法编码需额外位置向量(与词向量同维度),而 RoPE 直接复用 Q/K 的现有维度,避免特征空间稀释。同时生成新 Token 时不影响已有向量,完美适配对话模型的 KV Cache 机制
- 数值稳定性:旋转矩阵是正交矩阵,不会改变向量模长,避免加法操作可能导致的数值震荡
- 外推性:RoPE 的外推能力源于其对旋转角度的动态计算,而非依赖预定义的位置向量表,也就是说,利用 RoPE 的相对位置自洽性,将 “未见的绝对位置” 转化为 “已见的相对距离模式”,从而实现长度外推
相对位置自洽性如何解决位置混肴?
在传统正余弦编码中,位置向量与词向量相加后,经过 Attention 层的线性变换( Q/K 的线性投影),原始位置编码的远程衰减特性会被破坏。例如,假设两个词向量 xm 和 xn 的相对距离为 s = m - n,它们的位置编码内积本应随 s 增大而衰减,但线性变换后,Q 和 K的点积可能出现高频震荡或异常增大,导致模型无法区分长距离依赖关系;而 RoPE 通过旋转矩阵实现相对位置编码,仅保留相对距离 s = m - n的信息,与绝对位置 m 和 n 无关。这种设计确保:
- 内积值仅由相对距离决定:当两个词的相对位置固定时,无论它们在序列中如何平移(例如从位置 1 - 3移动到位置 5 - 7),点积结果保持不变;
- 天然远程衰减:随着 s 增大,呈现周期性衰减,符合自然语言中 “近邻词关联更强” 的规律
相比之下,传统加法编码的点积会混合绝对位置和词向量特征,导致模型对超出训练长度的位置(如s > L_train)产生 “未训练的旋转角度”,引发 Attention 分数异常波动
RoPE 如何实现外推?
- 训练时学习 “旋转周期”:RoPE 将 d_head 维度分为 d/2 组,每组对应不同频率的旋转角度 θ = 10000^(-2 * i / d),高频维度(小 i)旋转快(周期短),在训练长度内已覆盖完整周期;低频维度(大 i)旋转慢(周期长),仅覆盖部分圆弧
- 测试时外推 “未训练过的圆弧”:当序列长度超过训练时的 L_train,高频维度因周期短,仍能在已训练的圆弧上找到对应点;低频维度则可能进入“未训练的圆弧区域”,导致外推性能下降
- 为了解决上面的问题,通过 位置内插(PI)(将位置索引 s 缩放到 [0, L_train],例如将 4096 长度映射到 2048 训练长度,避免低频维度的 OOD 问题) 和 NTK-Aware(对高频维度保持原频率(外推),对低频维度进行缩放(内插),模拟时钟“秒针快转、时针慢转”的特性,平衡高频细节与低频稳定性) 等技术通过调整旋转频率,将超长序列的位置映射到训练过的圆弧范围内。也就是上面说的,利用 RoPE 的相对位置自洽性,将 “未见的绝对位置” 转化为 “已见的相对距离模式”,从而实现长度外推
除了 RoPE,还有这两种位置编码可以了解一下
- AliBi:无需位置向量,直接对注意力分数施加与距离成正比的惩罚偏置 -m * | i - j | ,其中 m 是头专属斜率参数。实现简单且外推性优异,但在长序列任务中性能略逊于 RoPE
- p-RoPE:对 RoPE 的改进,仅对部分维度(如75%)施加旋转,保留高维语义信息,解决长距离依赖时的语义丢失问题,在 Gemma 等模型中验证了效果提升