通俗理解RoPE、2D-RoPE、M-RoPE

大模型向量数据库云通信

本文通过将这些方法可视化呈现为旋转操作和维度拆分,能让旋转位置编码(RoPE)、二维旋转位置编码(2D-RoPE)以及多模态旋转位置编码(M-RoPE)的核心概念更直观、更易于理解。

为什么需要位置嵌入?

假设有两个语言模型:一个一次只能处理一个词,另一个则可以并行处理所有词。

现在,有一个词序列,比如“Dog eats food”。

  • 对于第一个模型,输入的顺序很重要,因为它必须先处理“Dog”,再处理“eats”,最后处理“food”。但显然,这样既缓慢又低效!
  • 对于第二个模型,输入的顺序不重要,因此可以一次性输入所有词,甚至是乱序的,比如“food”、“Dog”、“eats”。由于这个模型可以并行处理所有词,所以速度快得多。

第二个模型的问题在于它不知道词的顺序。因此,需要向输入嵌入中添加一些位置信息。

现在,想象一下,用N个嵌入向量代替词,每个向量的维度为n\_dim。举个例子:4个嵌入向量,每个的维度n\_dim=8,且初始化为1:

picture.image

现在将位置嵌入0、1、2、3分别应用到每个嵌入向量上。还使用了一种假想的方法,即简单地将位置索引加到每个嵌入向量上。因此,第一个嵌入向量就会是1+0,第二个是1+1,以此类推。

picture.image

当然,这只是一个用于说明该概念的简单例子。实际上,这种方法是行不通的。那么,该怎么做呢?

RoPE

picture.image

RoPE是旋转位置嵌入(Rotary Position Embedding)的缩写。目前广泛运用在LLM中,其是一种在Transformer模型的输入嵌入中编码位置信息的方法。

RoPE的工作原理很简单,就是在二维空间中旋转输入嵌入向量。这里不深入数学细节,但举一个简单的例子:

输入向量为[x=0, y=1],每个位置会将该嵌入向量逆时针旋转20度(

)。

picture.image

在现实世界中,并非只有2个维度,而是有n_dim个维度。例如,n_dim = 4:

picture.image

那么,如何在n维空间中旋转一个向量呢?答案很简单:将这个向量拆分成多个二维对。

picture.image

为了简化说明,会用箭头来替代每一对(二维对):

picture.image

现在有趣的部分来了:不会用相同的角度旋转每个向量,因为这样很快就会耗尽所有可能的角度

理解这一点的最佳方式就像看待一个时钟🕓:秒针每转一整圈,分针只转动一小部分。

在RoPE中,旋转的量被称为频率(记为

),其定义为:

其中,

是二维对的索引(范围从

),base是一个预定义的常数,通常为10000。

要确定每个二维对的旋转量

θ

,只需将位置索引

乘以频率

即可:

为简单起见,假设第一对的频率

仍为20°,第二对的频率

为10°:

picture.image

如所见,第一对的旋转速度比第二对快,就像时钟的秒针比分针转得快一样。

就像时钟的三根指针可以用来表示一天中的86400秒那样,可以用同样的思路在RoPE中表示所有的n维维度。

gemma的RoPE实现如下:

  
def \_compute\_default\_rope\_parameters(  
    config: Optional[PretrainedConfig] = None,  
    device: Optional["torch.device"] = None,  
    seq\_len: Optional[int] = None,  
    **rope\_kwargs,  
) -> tuple["torch.Tensor", float]:  
    """  
    Computes the inverse frequencies according to the original RoPE implementation  
    Args:  
        config ([`~transformers.PretrainedConfig`]):  
            The model configuration.  
        device (`torch.device`):  
            The device to use for initialization of the inverse frequencies.  
        seq\_len (`int`, *optional*):  
            The current sequence length. Unused for this type of RoPE.  
        rope\_kwargs (`Dict`, *optional*):  
            BC compatibility with the previous RoPE class instantiation, will be removed in v4.45.  
    Returns:  
        Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the  
        post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).  
    """  
    base = rope\_kwargs["base"]  
    dim = rope\_kwargs["dim"]  
    attention\_factor = 1.0  # Unused in this type of RoPE  
  
    # Compute the inverse frequencies  
    # 计算RoPE公式40中的theta,每2个维度共用一个inv\_freq  
    inv\_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim))  
    return inv\_freq, attention\_factor  
  
class GemmaRotaryEmbedding(nn.Module):  
    def \_\_init\_\_(self, config: GemmaConfig, device=None):  
        super().\_\_init\_\_()  
        self.rope\_type = "default"  
        self.max\_seq\_len\_cached = config.max\_position\_embeddings  
        self.original\_max\_seq\_len = config.max\_position\_embeddings  
        self.config = config  
        # 计算前面一半维度旋转角度theta,用于和position\_ids相乘  
        inv\_freq, self.attention\_scaling = \_compute\_default\_rope\_parameters(self.config, device)  
        self.register\_buffer("inv\_freq", inv\_freq, persistent=False)  
        self.original\_inv\_freq = self.inv\_freq  
    def forward(self, x, position\_ids):  
        inv\_freq\_expanded = self.inv\_freq[None, :, None].float().expand(position\_ids.shape[0], -1, 1).to(x.device)  
        position\_ids\_expanded = position\_ids[:, None, :].float()  
        device\_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps"else"cpu"  
        with torch.autocast(device\_type=device\_type, enabled=False):  # Force float32  
            #这里的freqs只是前面一半维度的  
            freqs = (inv\_freq\_expanded.float() @ position\_ids\_expanded.float()).transpose(1, 2)  
            # 在hidde\_dim维度拼接起来之后freqs最后一维的维度才等于hidden\_size  
            emb = torch.cat((freqs, freqs), dim=-1)  
            # 计算 cos(m*theta)和sin(m*theta)  
            cos = emb.cos()  
            sin = emb.sin()  
  
        return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)  
  
def rotate\_half(x):  
    """  
    Rotates half the hidden dims of the input.  
    将输入x后hidden\_dim的半部分取反并拼接到原来前半部分的前面  
    """  
    x1 = x[..., : x.shape[-1] // 2]  
    x2 = x[..., x.shape[-1] // 2 :]  
    return torch.cat((-x2, x1), dim=-1)  
  
def apply\_rotary\_pos\_emb(q, k, cos, sin, position\_ids=None, unsqueeze\_dim=1):  
    """  
    Applies Rotary Position Embedding to the query and key tensors.  
    将q,k乘以得到的旋转矩阵cos,sin  
    """  
    cos = cos.unsqueeze(unsqueeze\_dim)  
    sin = sin.unsqueeze(unsqueeze\_dim)  
    q\_embed = (q * cos) + (rotate\_half(q) * sin)  
    k\_embed = (k * cos) + (rotate\_half(k) * sin)  
    return q\_embed, k\_embed  

2D-RoPE

到目前为止,只讨论了RoPE,它是一种一维位置嵌入方法。这对于一维序列很有用,比如文本。

但如果想将RoPE用于二维序列(例如图像),该怎么办呢?

二维RoPE(2D-RoPE)是RoPE的一种简单扩展,在这种方法中,每个输入向量都有一个二维位置

picture.image

回到时钟的类比,一个简单的思路是使用两个时钟,一个对应y轴,另一个对应x轴。

为了说明这一点,将初始示例的维度n_dim加倍,这样就有n_dim = 8:

picture.image现在它被表示为4对二维向量:

picture.image

其思路是将该向量进一步拆分为两部分,一部分对应y轴,另一部分对应x轴。

picture.image

假设这4个向量的位置列表如下:

  • [0, 0]
  • [0, 1]
  • [1, 2]
  • [1, 3]

使用一组40°和20°的角度值,可以像这样独立旋转每个部分的向量:

picture.image

据所知,这种二维旋转位置编码(2D-RoPE)方法被用于Llama 4模型的视觉编码器中。

2D-RoPE with interleaved frequency

在之前的示例中,对两个轴使用了相同的角度值(40°和20°)。但如果想为每个轴使用不同的频率呢?

以Mistral的Pixtral模型为例:

  • 首先为所有二维向量对创建一个频率列表,例如:40°、30°、20°、10°
  • 然后将这些频率按轴交错分配,这样y轴得到40°、20°,x轴得到30°、10°。

picture.image

巧妙之处在于,无需先构建一个频率列表(例如40°、30°、20°、10°),再从中挑选奇数位或偶数位的频率值,只需调整n_dim参数值和频率的缩放比例,就能得到相同的结果。可以查看这个PR(拉取请求)了解的具体实现方式。

M-RoPE

picture.image

Qwen2VL的M-RoPE

M-RoPE是Multimodal-RoPE(多模态旋转位置编码)的缩写,最初由Qwen2VL模型提出。

M-RoPE扩展了二维旋转位置编码(2D-RoPE)的理念,不过现在每个位置包含的维度不止2个。例如,可以有三维

,甚至更多维度。

其核心思想是,不再将嵌入向量拆分为2部分,而是拆分为……没错,拆分为n部分,其中n是每个位置的维度数量。

如果仔细查看Qwen2VL的config.json文件,会看到一个名为mrope_section的配置,其中包含3个数值。每个数值代表每个部分的二维对数量。

  
"rope\_scaling": {  
    "type": "mrope",  
    "mrope\_section": [  
      16,  
      24,  
      24  
    ]  
  },  

为了便于理解,举一个简单的例子:当嵌入向量的维度n\_dim=8时,最终会得到4对二维向量:

picture.image

假设的mrope_section配置为

,可以将嵌入向量拆分为3个部分:

picture.image

然后,使用与二维旋转位置编码(2D-RoPE)中所解释的相同方法,对每个部分独立应用旋转位置编码(RoPE)。

picture.image

参考文献

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
CV 技术在视频创作中的应用
本次演讲将介绍在拍摄、编辑等场景,我们如何利用 AI 技术赋能创作者;以及基于这些场景,字节跳动积累的领先技术能力。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论