RoPE简单解析

简介

因为RoPE的优异性能,其已成为各种大模型中位置编码的首选,包括多模态模型;在一些多模态模型或视频理解模型中,甚至会用到多维度RoPE。虽然RoPE已广泛应用,之前也看了不少针对其原理解析的文章,特别是苏神的推导帖,可能是我个人能力问题,也可能是太过于追求原理的问题,对RoPE一直没有理解透彻,总感觉差那么一点。本文会适当弱化RoPE为什么会有效的原理推导,将重点放在RoPE应该如何实现以及具体的实现步骤和一些不同实现方式的解析。

RoPE的核心思想是通过旋转操作将位置信息融入到词向量。在复数域中,一个复数由实部和虚部组成,故一个复数可以看作复数域中的向量。将一个复数与复数 e i θ = cos ⁡ θ + i sin ⁡ θ e^{i\theta}=\cos{\theta}+i \sin{\theta} eiθ=cosθ+isinθ相乘时,相当于在复平面上绕原点旋转了 θ \theta θ度角。假设有一个复数 v = x + i y v=x+iy v=x+iy,按上述说明,将 v v v e i θ e^{i\theta} eiθ相乘就是将 v v v绕原点旋转了 θ \theta θ度角,可以通过公式推导验证:
v ′ = v ∗ e i θ = ( x + i y ) ∗ ( cos ⁡ θ + i sin ⁡ θ ) = cos ⁡ θ x + i sin ⁡ θ x + i cos ⁡ θ y − sin ⁡ θ y = ( cos ⁡ θ x − sin ⁡ θ y ) + i ( sin ⁡ θ x + cos ⁡ θ y ) \begin{align*} v'&=v*e^{i\theta} \\ &=(x+iy)*(\cos{\theta}+i \sin{\theta}) \\ &=\cos{\theta}x+i\sin{\theta}x+i\cos{\theta}y-\sin{\theta}y \\ &=(\cos{\theta}x-\sin{\theta}y)+i(\sin{\theta}x+\cos{\theta}y) \tag{1} \end{align*} v=veiθ=(x+iy)(cosθ+isinθ)=cosθx+isinθx+icosθysinθy=(cosθxsinθy)+i(sinθx+cosθy)(1)

通过公式(1)有 x ′ = cos ⁡ θ x − sin ⁡ θ y , y ′ = sin ⁡ θ x + cos ⁡ θ y x'=\cos{\theta}x-\sin{\theta}y,y'=\sin{\theta}x+\cos{\theta}y x=cosθxsinθy,y=sinθx+cosθy。从二维向量的角度看,变换前后的复数 v , v ′ v,v' v,v其实均可以看作列向量 [ x y ] , [ x ′ y ′ ] \begin{bmatrix} x \\ y \end{bmatrix},\begin{bmatrix} x' \\ y' \end{bmatrix} [xy][xy],公式(1)表示 v ′ v' v是由 v v v旋转而来,且恰好对应了二维向量空间的旋转矩阵,如下所示:
[ x ′ y ′ ] = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] [ x y ] (2) \begin{equation} \begin{bmatrix} x' \\ y' \end{bmatrix}=\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} \end{equation} \tag2 [xy]=[cosθsinθsinθcosθ][xy](2)

如公式(2)所示,在二维向量空间中,将一个向量绕原点旋转 θ \theta θ度就是乘以一个旋转矩阵,即 R ( θ ) = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] R(\theta)=\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} R(θ)=[cosθsinθsinθcosθ]。通过上述推导可知,二维向量空间中的旋转和复数乘法在进行旋转时是等价的。

RoPE是将隐向量序列中不同位置的token的特征向量与不同的旋转角对应的旋转矩阵相乘,即将不同位置token的特征向量旋转不同的角度。注意力计算的主要部分是token向量之间的点积运算,可以通过推导展示RoPE是如何在自注意力计算中引入相对位置信息的。

假设目前token的维度为2,有序列中m处的查询向量 q m = [ q m , 1 q m , 2 ] q_m=\begin{bmatrix} q_{m,1} \\ q_{m,2} \end{bmatrix} qm=[qm,1qm,2]和n处的键向量 k n = [ k n , 1 k n , 2 ] k_n=\begin{bmatrix} k_{n,1} \\ k_{n,2} \end{bmatrix} kn=[kn,1kn,2],设旋转基础角度为 θ \theta θ,序列中不同位置token的旋转角由位置索引与基础角度相乘得到,即上述两个向量的旋转角度为别是 m θ , n θ m\theta,n\theta mθ,nθ,RoPE就是给不同位置的token向量与对应位置的旋转角的旋转矩阵相乘,基于前文的推理,有,
q m ′ = [ cos ⁡ ( m θ ) − sin ⁡ ( m θ ) sin ⁡ ( m θ ) cos ⁡ ( m θ ) ] [ q m , 1 q m , 2 ] = R ( m θ ) q m (3) \begin{equation} q'_m=\begin{bmatrix} \cos(m\theta) & -\sin(m\theta) \\ \sin(m\theta) & \cos(m\theta) \end{bmatrix} \begin{bmatrix} q_{m,1} \\ q_{m,2} \end{bmatrix} = R(m\theta) q_m \end{equation} \tag3 qm=[cos(mθ)sin(mθ)sin(mθ)cos(mθ)][qm,1qm,2]=R(mθ)qm(3)
k n ′ = [ cos ⁡ ( n θ ) − sin ⁡ ( n θ ) sin ⁡ ( n θ ) cos ⁡ ( n θ ) ] [ k n , 1 k n , 2 ] = R ( n θ ) k m (4) \begin{equation} k'_n=\begin{bmatrix} \cos(n\theta) & -\sin(n\theta) \\ \sin(n\theta) & \cos(n\theta) \end{bmatrix} \begin{bmatrix} k_{n,1} \\ k_{n,2} \end{bmatrix} = R(n\theta) k_m \end{equation} \tag4 kn=[cos(nθ)sin(nθ)sin(nθ)cos(nθ)][kn,1kn,2]=R(nθ)km(4)

旋转后的两个向量进行以下点积计算:
q m ′ T k n ′ = ( R ( m θ ) q m ) T ⋅ R ( n θ ) k n = q m T ⋅ R ( m θ ) T ⋅ R ( n θ ) ⋅ k n = q m T ⋅ [ cos ⁡ ( m θ ) sin ⁡ ( m θ ) − sin ⁡ ( m θ ) cos ⁡ ( m θ ) ] [ cos ⁡ ( n θ ) − sin ⁡ ( n θ ) sin ⁡ ( n θ ) cos ⁡ ( n θ ) ] ⋅ k n = q m T ⋅ [ cos ⁡ ( m θ ) cos ⁡ ( n θ ) + sin ⁡ ( m θ ) sin ⁡ ( n θ ) − cos ⁡ ( m θ ) sin ⁡ ( n θ ) + sin ⁡ ( m θ ) cos ⁡ ( n θ ) − sin ⁡ ( m θ ) cos ⁡ ( n θ ) + cos ⁡ ( m θ ) sin ⁡ ( n θ ) sin ⁡ ( m θ ) sin ⁡ ( n θ ) + cos ⁡ ( m θ ) cos ⁡ ( n θ ) ] ⋅ k n = q m T ⋅ [ cos ⁡ ( ( n − m ) θ ) − sin ⁡ ( ( n − m ) θ ) sin ⁡ ( ( n − m ) θ ) cos ⁡ ( ( n − m ) θ ) ] ⋅ k n = q m T ⋅ R ( ( n − m ) θ ) ⋅ k n \begin{align*} q_m^{'T}k'_n &= (R(m\theta) q_m)^T \cdot R(n\theta) k_n \\ &= q^T_m \cdot R(m\theta)^T \cdot R(n\theta) \cdot k_n \\ &= q^T_m \cdot \begin{bmatrix} \cos(m\theta) & \sin(m\theta) \\ -\sin(m\theta) & \cos(m\theta) \end{bmatrix} \begin{bmatrix} \cos(n\theta) & -\sin(n\theta) \\ \sin(n\theta) & \cos(n\theta) \end{bmatrix} \cdot k_n \\ &= q^T_m \cdot \begin{bmatrix} \cos(m\theta)\cos(n\theta) + \sin(m\theta)\sin(n\theta) & -\cos(m\theta)\sin(n\theta) + \sin(m\theta)\cos(n\theta) \\ -\sin(m\theta)\cos(n\theta) + \cos(m\theta)\sin(n\theta) & \sin(m\theta)\sin(n\theta) + \cos(m\theta)\cos(n\theta) \end{bmatrix} \cdot k_n \\ &= q^T_m \cdot \begin{bmatrix} \cos((n - m)\theta) & -\sin((n - m)\theta) \\ \sin((n - m)\theta) & \cos((n - m)\theta) \end{bmatrix} \cdot k_n \\ &= q^T_m \cdot R((n - m)\theta) \cdot k_n \tag5 \end{align*} qmTkn=(R(mθ)qm)TR(nθ)kn=qmTR(mθ)TR(nθ)kn=qmT[cos(mθ)sin(mθ)sin(mθ)cos(mθ)][cos(nθ)sin(nθ)sin(nθ)cos(nθ)]kn=qmT[cos(mθ)cos(nθ)+sin(mθ)sin(nθ)sin(mθ)cos(nθ)+cos(mθ)sin(nθ)cos(mθ)sin(nθ)+sin(mθ)cos(nθ)sin(mθ)sin(nθ)+cos(mθ)cos(nθ)]kn=qmT[cos((nm)θ)sin((nm)θ)sin((nm)θ)cos((nm)θ)]kn=qmTR((nm)θ)kn(5)

上述公式(5)推导过程要使用高中数学知识–三角函数和差定理。可以看到,两个旋转不同角度后向量的点积与它们之间旋转的角度之差有关系,是一种相对位置的体现,即体现了RoPE给序列中不同位置token引入了相对位置信息。

注意:上述旋转均是逆时针旋转!

拆解

上述只是对RoPE的原理进行了简短解释,尽可能直白地说明RoPE原理,但上述讨论局限在二维空间内,而LLMs模型中token向量的维度都很大,接下来对常规的RoPE实现进行拆解,将整个实现过程解剖出来。

  1. 为了沿用二维旋转矩阵的性质,RoPE会将一个token的高维向量看作多个复数,假设向量特征维度为 d d d(基本所有LLMs中的特征维度数是偶数),那么会将一个 d d d维的token特征向量视为 d / 2 d/2 d/2个复数
  • 假设一个token向量为 [ 1 , 2 , 3 , 4 ] [1,2,3,4] [1,2,3,4],那么 [ 1 , 2 ] [1,2] [1,2]是第一个复数, [ 3 , 4 ] [3,4] [3,4]是第二个复数
  • 同一个token中的不同复数对的旋转角度不同
  • 基础旋转角 θ i = 1000 0 − 2 i / d = 1 1000 0 i d 2 \theta_i=10000^{-2i/d}=\frac{1}{10000^{\frac{i}{\frac{d}{2}}}} θi=100002i/d=100002di1;注意,同一token向量中不同位置复数的旋转角是由 i i i来决定的。上文已经说到,一个token向量会分成 d / 2 d/2 d/2个复数, i ∈ [ 0 , d / 2 ) i \in [0,d/2) i[0,d/2),所以 θ i \theta_i θi就对应第 i i i对复数的旋转角
    • 10000是一个超参数
  1. 同一序列中不同位置的token向量的旋转角也不同,就是基础旋转角与token对应的序列索引乘积,即一个token在序列中的索引为m,那么其对应的最终的旋转角为 θ i ⋅ m \theta_i \cdot m θim
  • 实现时一般会先将 θ i \theta_i θi计算出来,再基于序列长度获取不同位置token索引的一维向量,然后两者相乘,得到不同位置token中不同复数维度的准确旋转角度
  1. 将输入沿着特征维度,按顺序每两个为一组作为一对复数进行拆分,向量维度从[batch_size, seq_len, dim]–>[batch_size, seq_len, dim//2, 2]
  • 最后一个维度中,第一组向量为所有复数的实部,第二组向量为所有复数的虚部
  1. 按照二维矩阵中的计算方式,进行旋转矩阵乘法,然后将最终的实部和虚部重新合并为原来的维度[batch_size, seq_len, dim],即完成了RoPE的应用

上述对RoPE的具体操作过程进行了叙述,以下是一种较原始的朴素实现:

import torch


def get_rotary_matrix(seq_len, dim, base=10000):
    """生成RoPE的旋转矩阵"""
    # 生成不同频率的正弦和余弦值
    theta = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))  # shape为[dim//2]
    # 生成位置索引
    position = torch.arange(seq_len).float()  # shape为[seq_len]
    # 计算每个位置和维度对应的角度
    theta = torch.outer(position, theta)  # 计算外积,其中第(i, j)个元素是position[i] * theta[j];shape为[seq_len, dim//2]
    # 计算正弦和余弦值
    cos = torch.cos(theta)  # shape为[seq_len, dim//2]
    sin = torch.sin(theta)  # shape为[seq_len, dim//2]
    return cos, sin


def apply_rotary_embedding(x, cos, sin):
    """应用旋转位置编码"""
    # 假设x的形状为[batch_size, seq_len, dim]
    # 将向量视为复数,每两个维度一组
    x_reshape = x.view(*x.shape[:-1], -1, 2)  # shape为[batch_size, seq_len, dim//2, 2],即沿着特征维度拆分
    
    # 构建正弦和余弦矩阵,使其与x_reshape形状匹配
    cos_expanded = cos.view(1, cos.shape[0], cos.shape[1], 1)  # shape为[1, seq_len, dim//2, 1]
    sin_expanded = sin.view(1, sin.shape[0], sin.shape[1], 1)  # shape为[1, seq_len, dim//2, 1]
    
    # 旋转操作(复数乘法)
    # [x_real, x_imag] * (cos + i*sin) = [x_real*cos - x_imag*sin, x_real*sin + x_imag*cos]
    x_out_1 = x_reshape[:, :, :, 0:1] * cos_expanded - x_reshape[:, :, :, 1:2] * sin_expanded
    x_out_2 = x_reshape[:, :, :, 0:1] * sin_expanded + x_reshape[:, :, :, 1:2] * cos_expanded
    
    # 合并结果
    x_out = torch.cat([x_out_1, x_out_2], dim=-1)  # shape为[batch_size, seq_len, dim//2, 2]
    return x_out.view(*x.shape)


# 示例用法
def apply_rope(x):
    """对输入向量应用RoPE位置编码"""
    batch_size, seq_len, dim = x.shape
    cos, sin = get_rotary_matrix(seq_len, dim)
    return apply_rotary_embedding(x, cos, sin)


# 测试代码
if __name__ == "__main__":
    # 创建一个随机输入张量
    batch_size, seq_len, dim = 2, 10, 512
    x = torch.randn(batch_size, seq_len, dim)
    
    # 应用RoPE
    x_with_rope = apply_rope(x)
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {x_with_rope.shape}")

一些tricks

  • 在对输入的嵌入维度上应用RoPE时,当特征维度很长,或者希望节约资源资源时,可以不对所有维度全部应用,而是可以设置一个比例,即只在前 d r o p e d_{rope} drope维度上应用RoPE,后面保持原始数值不变
  • 在计算 θ i \theta_i θi时原始论文使用的base为10000,但可以对其进行缩放,实现长度外推;如增加base,可以处理训练过程中未见过的长序列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值