手把手带你从零推导旋转位置编码RoPE

在这里插入图片描述

RNN每个step的隐状态都取决于上一个step的输出,这种连续的状态转移方式使得RNN天然带有位置信息。而Transformer仅依靠Attention机制来关注序列中不同token之间的相关性,如果只使用token embedding就无法获得句子中字与字之间的位置信息,也就是说如果没有位置编码,输入“我不爱你”与“不我爱你”的效果是相同的,因此我们要在里面额外引入位置信息。下面我就为大家介绍位置编码的发展过程。

绝对位置编码

通常所说的大模型的模型结构一般基于 Transformer 改进而来。Transformer 结构如下:

在这里插入图片描述

最初的Transformer设计中,绝对位置编码会加到输入中:在输入的第k个向量 x k x_{k} xk中加入位置向量 p k p_{k} pk变为 x k + p k x_{k}+p_{k} xk+pk,其中 p k p_{k} pk只依赖于token位于句子中的编号k。

函数式绝对位置编码

绝对位置编码在进行Attention前使用正余弦函数来表示绝对位置,公式如下:

{ P E p o s , 2 i = sin ⁡ ( p o s / 1000 0 2 i / d m o d e l ) P E p o s , 2 i + 1 = cos ⁡ ( p o s / 1000 0 2 i / d m o d e l ) \left\{\begin{array}{ll}\boldsymbol{PE}_{pos, 2 i} & =\sin \left(pos / 10000^{2 i / d_{model}}\right) \\ \boldsymbol{PE}_{pos, 2 i+1} & =\cos \left(pos / 10000^{2i / d_{model}}\right)\end{array}\right. {PEpos,2iPEpos,2i+1=sin(pos/100002i/dmodel)=cos(pos/100002i/dmodel)

其中PE表示位置编码,pos表示一句话中token的位置,每个token的位置编码是一个向量,i表示这个向量中每个元素的index,这个向量的奇数位置元素使用余弦值,偶数位置元素使用正弦值, d m o d e l d_{model} dmodel表示位置编码向量的维度。

在这里插入图片描述

这种编码的优点是相邻的位置编码向量很相似,较远的位置编码向量差异很大,说明基于正余弦函数的绝对位置可以表现位置的相关性,而且可以直接计算提高效率。

训练式绝对位置编码

基于万物皆可训练的思想诞生了训练式绝对位置编码,它根据位置信息编码id,再通过Embedding层,学习该位置Embedding参数,比如Bert最大长度为512,编码维度为768,那么就初始化一个512×768的矩阵作为位置向量,让它随着训练过程更新。

从BERT提出后,大部分模型的位置编码都遵循这种方式,但这种位置编码有一个缺陷,就是它不具备外推性能,是指一旦我们在预测时的序列长度超过了训练阶段的长度,它的位置信息就位于模型的盲区,需要重新训练更长的位置编码。

相对位置编码

绝对和相对在概念上理解是很简单的,但它们在Transformer的实现上是完全不一样的。

  • 由于绝对位置编码只关注单个位置信息,因此它的实现通常在输入层,可以通过简单的向量相加融入模型
  • 而相对位置一定是pair对(某两个token之间)的信息,因此它无法直接在输入层实现,通常是通过改变Attention_score的计算方式来实现

在这里插入图片描述

比如在LLaMA模型结构中,计算相对位置编码就是在计算Attention_score的同时实现的。而相对位置编码在计算方法上也可以分为训练式相对位置编码与函数式相对位置编码。

训练式相对位置编码

训练式的代表就是Deberta,它的做法是对于第m和第n个位置的token,其相对位置可以表示为:

d i s t a n c e ( i , j ) = c l i p ( i − j , d m i n , d m a x ) distance(i,j) = clip(i-j,d_{min}, d_{max}) distance(i,j)=clip(ij,dmin,dmax)

即两个token之间的相对距离,由最大距离与最小距离做约束(相对位置不能超过最大距离 d m a x d_{max} dmax或小于最小距离 d m i n d_{min} dmin),绝对位置向量需要有模型能容纳句子的最大长度(max_len)个向量才能决定,现在的大模型能识别上下文的长度为128k,若用绝对位置向量我们要用128k个向量来代表位置向量,而相对位置向量只需 d m a x − d m i n + 1 d_{max} - d_{min} + 1 dmaxdmin+1个位置向量来表示即可。

在计算两个token之间的attention值时,假设两个token的向量表示分别为 q i , k i q_i,k_i qi,ki q j , k j q_j,k_j qj,kj,根据上式得到它们的距离为 d ( i , j ) , d ( j , i ) d(i,j),d(j,i) d(i,j),d(j,i),然后学习得到它们的相对距离的位置向量为 q d ( i , j ) , k d ( i , j ) q_{d(i,j)},k_{d(i,j)} qd(i,j),kd(i,j) q d ( j , i ) , k d ( j , i ) q_{d(j,i)},k_{d(j,i)} qd(j,i),kd(j,i),就可以计算它们的attention_score:

A i , j = ( q i , q d ( i , j ) ) × ( k j , k d ( j , i ) ) A_{i, j}=\left(q_{i}, q_{d(i, j)}\right) \times\left(k_{j}, k_{d(j, i)}\right) Ai,j=(qi,qd(i,j))×(kj,kd(j,i))

这样一来,只需要有限个位置编码,就可以表达出任意长度的相对位置(因为进行了截断),不管是选择可训练式的还是三角函数式的,都可以达到处理任意长度文本的需求。这就是最基础的训练式相对位置编码方式,本质上是学习相对位置的Embedding,再在Attention计算时将位置信息融入其中。

函数式相对位置编码

函数式相对位置编码,不再需要额外的训练向量,它的本质在于通过给原始的 q , k q,k q,k向量经过一系列变换来表示其相对位置,我从简单二维形式推导至高维形式。

核心想法与理论基础是:

  1. 一个二维向量** ( x , y ) (x,y) (x,y)的内积可由复数$(x+y \cdot i) $**乘法表示。以下为向量内积:

q = ( x 1 , y 1 ) k = ( x 2 , y 2 ) q ⋅ k = x 1 ⋅ x 2 + y 1 ⋅ y 2 \begin{align*} q &= (x_1, y_1) \\ k &= (x_2, y_2) \\ q \cdot k &= x_1 \cdot x_2 + y_1 \cdot y_2 \end{align*} qkqk=(x1,y1)=(x2,y2)=x1x2+y1y2

以下为复数乘法:

q = x 1 + y 1 i k = x 2 + y 2 i ⟨ q , k ⟩ = ( x 1 + y 1 i ) ⋅ ( x 2 + y 2 i ) = ( x 1 ⋅ x 2 − y 1 ⋅ y 2 ) + ( x 1 ⋅ y 2 + y 1 ⋅ x 2 ) i \begin{align*} q &= x_1 + y_1 i \\ k &= x_2 + y_2 i \\ \langle q, k \rangle &= (x_1 + y_1 i) \cdot (x_2 + y_2 i) \\ &= (x_1 \cdot x_2 - y_1 \cdot y_2) + (x_1 \cdot y_2 + y_1 \cdot x_2) i \end{align*} qkq,k=x1+y1i=x2+y2i=(x1+y1i)(x2+y2i)=(x1x2y1y2)+(x1y2+y1x2)i

可以看到向量内积与复数乘法的实数部分相差一个负号,所以可以乘 k k k的共轭复数 k ∗ = x 2 − y 2 i k^* = x_2-y_2i k=x2y2i,可得:

⟨ q , k ∗ ⟩ = ( x 1 + y 1 i ) ⋅ ( x 2 − y 2 i ) = ( x 1 ⋅ x 2 + y 1 ⋅ y 2 ) + ( y 1 ⋅ x 2 − x 1 ⋅ y 2 ) i \begin{align*} \langle q, k^* \rangle &= (x_1 + y_1 i) \cdot (x_2 - y_2 i) \\ &= (x_1 \cdot x_2 + y_1 \cdot y_2) + (y_1 \cdot x_2 - x_1 \cdot y_2) i \end{align*} q,k=(x1+y1i)(x2y2i)=(x1x2+y1y2)+(y1x2x1y2)i

如此一来,一个二维向量 q m , k n q_m,k_n qm,kn(一句话中token所在位置分别为m和n)的内积可由复数乘法结果的实部来表示,记为下式:

⟨ q m , k n ⟩ = R e [ q m k n ∗ ] ⟨q_m,k_n⟩=Re[q_mk_n^*] qm,kn=Re[qmkn]

其中 k n ∗ k_n^* kn为共轭复数,右端为复数乘法, R e [ ] Re[] Re[]表示取结果的实部。上述运算本质上还是在实数范畴内的,只不过我们是借助复数来完成了推导而已。

  1. 二维平面向量** q ( x 1 , x 2 ) q(x_1,x_2) q(x1,x2)可由复数表示,对复数乘以 e i θ e^{i \theta} eiθ,相当于把该向量逆时针旋转角度 θ \theta θ**,只改变了向量的方向,但不改变向量的模长。公式与图示如下:

q e i θ = ( x 1 + x 2 ⋅ i ) e i θ qe^{i \theta} = (x_1+ x_2 \cdot i) e^{i \theta} qeiθ=(x1+x2i)eiθ

在这里插入图片描述

  1. 这么做就相当于给二维向量 q m , k n q_m,k_n qm,kn(一句话中token所在位置分别为m和n)配上了绝对位置编码**(** θ \theta θ**的计算依赖于token所在句中位置),然后做内积,可得:

⟨ q m e i θ , k n e i θ ⟩ = Re ⁡ [ ( q m e i θ ) ( k n e i θ ) ∗ ] = Re ⁡ [ q m k n ∗ e i θ ] \left\langle\boldsymbol{q}_{m} e^{\mathrm{i} \theta}, \boldsymbol{k}_{n} e^{\mathrm{i} \theta}\right\rangle=\operatorname{Re}\left[\left(\boldsymbol{q}_{m} e^{\mathrm{i} \theta}\right)\left(\boldsymbol{k}_{n} e^{\mathrm{i} \theta}\right)^{*}\right]=\operatorname{Re}\left[\boldsymbol{q}_{m} \boldsymbol{k}_{n}^{*} e^{\mathrm{i} \theta}\right] qmeiθ,kneiθ=Re[(qmeiθ)(kneiθ)]=Re[qmkneiθ]

上述** θ \theta θ**为:

θ = p o s 1000 0 2 i / d m o d e l = m − n 1000 0 2 i / d m o d e l \theta = \frac{pos}{10000^{2i/d_{model}}} =\frac{m-n}{10000^{2i/d_{model}}} θ=100002i/dmodelpos=100002i/dmodelmn

pos表示一句话中token的位置,所以由m与n决定,注意 θ \theta θ公式中的i表示这个向量中每个元素的index,不是虚部。由上述公式可知,内积只依赖于相对位置m-n,这样一来,我们得到了一种融绝对位置与相对位置于一体的位置编码方案:

  • 绝对编码是指:在计算中考虑了token在句子中的绝对位置
  • 相对编码是指:两个向量内积计算只依赖于向量所处句子中的相对位置m-n

与此同时,就巧妙地在QK计算过程中将绝对位置与相对位置融合到了一起:

在这里插入图片描述

在实际计算中把 e i θ e^{\mathrm{i} \theta} eiθ幂函数进行恒等转换,可知欧拉公式:

e i θ = c o s θ + s i n θ ⋅ i e^{i\theta} = cos\theta + sin\theta \cdot i eiθ=cosθ+sinθi

对于二维实数向量 q ( x , y ) q(x,y) q(x,y)乘以 e i θ e^{\mathrm{i} \theta} eiθ得:

q e i θ = ( x + y ⋅ i ) e i θ qe^{i \theta} =(x+ y \cdot i) e^{i \theta} qeiθ=(x+yi)eiθ

将欧拉公式代入复数旋转公式可得:

( x + y ⋅ i ) e i θ = ( x cos ⁡ θ − y sin ⁡ θ ) + ( x sin ⁡ θ + y cos ⁡ θ ) i (x+y \cdot i) e^{i \theta}=(x \cos \theta-y \sin \theta)+(x \sin \theta+y \cos \theta)i (x+yi)eiθ=(xcosθysinθ)+(xsinθ+ycosθ)i

这样就将幂函数的计算转化为三角函数的计算。

  1. 到这里,我们就完成了二维向量的RoPE位置编码,但是这种变换仅适用于二维向量,那如何才能扩展到高维空间呢?

直观的想法就是将高维向量拆分成多个二维向量的拼接,每两个二维向量之间做内积。设位置向量长度为d,每两个元素为一组二维向量,一共有d/2个组合,可以直接拼接作为d维度的旋转位置编码。

( cos ⁡ θ 0 − sin ⁡ θ 0 0 0 … 0 0 sin ⁡ θ 0 cos ⁡ θ 0 0 0 … 0 0 0 0 cos ⁡ θ 1 − sin ⁡ θ 1 … 0 0 0 0 sin ⁡ θ 1 cos ⁡ θ 1 … 0 0 … … … … … … … 0 0 0 0 … cos ⁡ θ d / 2 − 1 − sin ⁡ θ d / 2 − 1 0 0 0 0 … sin ⁡ θ d / 2 − 1 cos ⁡ θ d / 2 − 1 ) ( q 0 q 1 q 2 q 3 … q d − 2 q d − 1 ) \left(\begin{array}{ccccccc}\operatorname{cos} \theta_{0} & -\operatorname{sin} \theta_{0} & 0 & 0 & \ldots & 0 & 0 \\ \operatorname{sin} \theta_{0} & \operatorname{cos} \theta_{0} & 0 & 0 & \ldots & 0 & 0 \\ 0 & 0 & \operatorname{cos} \theta_{1} & -\operatorname{sin} \theta_{1} & \ldots & 0 & 0 \\ 0 & 0 & \operatorname{sin} \theta_{1} & \operatorname{cos} \theta_{1} & \ldots & 0 & 0 \\ \ldots & \ldots & \ldots & \ldots & \ldots & \ldots & \ldots \\ 0 & 0 & 0 & 0 & \ldots & \operatorname{cos} \theta_{d / 2-1} & -\operatorname{sin} \theta_{d / 2-1} \\ 0 & 0 & 0 & 0 & \ldots & \operatorname{sin} \theta_{d / 2-1} & \operatorname{cos} \theta_{d / 2-1}\end{array}\right)\left(\begin{array}{c}q_{0} \\ q_{1} \\ q_{2} \\ q_{3} \\ \ldots \\ q_{d-2} \\ q_{d-1}\end{array}\right) cosθ0sinθ00000sinθ0cosθ0000000cosθ1sinθ10000sinθ1cosθ1000000cosθd/21sinθd/210000sinθd/21cosθd/21 q0q1q2q3qd2qd1

既然向量拆分了,那我们就可以采用分块矩阵来进行变换,每个块只关注两个分量的信息,且每个块的幅角也可以不同。

  1. 因为采用矩阵相乘的方式来实现RoPE会浪费很多显存,因此在实际计算中采用逐位相乘的方式来实现

( q 0 q 1 q 2 q 3 ⋯ q d − 2 q d − 1 ) ⊗ ( cos ⁡ θ 0 cos ⁡ θ 0 cos ⁡ θ 1 cos ⁡ θ 1 ⋯ cos ⁡ θ d / 2 − 1 cos ⁡ θ d / 2 − 1 ) + ( − q 1 q 0 − q 3 q 4 ⋯ − q d − 1 q d − 2 ) ⊗ ( sin ⁡ θ 0 sin ⁡ θ 0 sin ⁡ θ 1 sin ⁡ θ 1 ⋯ sin ⁡ θ d / 2 − 1 sin ⁡ θ d / 2 − 1 ) \left(\begin{array}{c}q_{0} \\ q_{1} \\ q_{2} \\ q_{3} \\ \cdots \\ q_{d-2} \\ q_{d-1}\end{array}\right) \otimes\left(\begin{array}{c}\operatorname{cos} \theta_{0} \\ \operatorname{cos} \theta_{0} \\ \operatorname{cos} \theta_{1} \\ \operatorname{cos} \theta_{1} \\ \cdots \\ \operatorname{cos} \theta_{d / 2-1} \\ \operatorname{cos} \theta_{d / 2-1}\end{array}\right)+\left(\begin{array}{c}-q_{1} \\ q_{0} \\ -q_{3} \\ q_{4} \\ \cdots \\ -q_{d-1} \\ q_{d-2}\end{array}\right) \otimes\left(\begin{array}{c}\operatorname{sin} \theta_{0} \\ \operatorname{sin} \theta_{0} \\ \operatorname{sin} \theta_{1} \\ \operatorname{sin} \theta_{1} \\ \cdots \\ \operatorname{sin} \theta_{d / 2-1} \\ \operatorname{sin} \theta_{d / 2-1}\end{array}\right) q0q1q2q3qd2qd1 cosθ0cosθ0cosθ1cosθ1cosθd/21cosθd/21 + q1q0q3q4qd1qd2 sinθ0sinθ0sinθ1sinθ1sinθd/21sinθd/21

至此,整个位置编码的发展过程与旋转编码的推导介绍完了。技术发展重要的原因就是因为当前技术解决不了目前的问题,希望大家在学习过程中可以带着“这个新技术解决了什么问题”的思维模式去学习。

更多AI干货请扫码关注公众号「AI有温度」获取
在这里插入图片描述

  • 47
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值