LLM(六)——Position Encoding

RNN的结构包含了序列的时序信息,而Transformer却完全把时序信息给丢掉了,比如“狗咬我”,和“我咬狗”,两者的意思千差万别,故为了解决时序的问题,Transformer的作者用了一个绝妙的办法:位置编码(Positional Encoding)

一、绝对位置编码(Absolute Positional Encoding)

1.1 简介

将每个位置编号,从而每个编号对应一个向量,最终通过结合位置向量和词向量,作为输入embedding,就给每个词都引入了一定的位置信息,这样Attention就可以分辨出不同位置的词了。

  1. 如果简单粗暴的话,直接给每个向量分配一个数字,比如1到1000之间
  2. 也可以用one-hot编码表示位置

  1. <<Attention Is All You Need>>论文中作者通过sin函数和cos函数交替来创建 positional encoding,其计算positional encoding的公式如下

其中,pos相当于是每个token在整个序列中的位置,相当于是0, 1, 2, 3…(看序列长度是多大,比如10,比如100),代表位置向量的维度(也是词embedding的维度,transformer论文中设置的512维)。至于是embedding向量的位置下标对2求商并取整,它的取值范围是,比如

位置向量的第多少维(0 2 4等偶数维用sin函数计算) i 2i 2i+1
0 i = 0 // 2 = 0 2i = 0
1 i = 1 //2 =0 2i = 0 2i+1 = 1
2 i = 2 // 2 = 1 2i = 2
3 i = 3 // 2 = 1 2i = 2 2i+1 = 3
4 i = 4 // 2 = 2 2i = 4
5 i = 5//2 = 2 2i = 4 2i + 1 =5
….
510 i = 510 // 2 = 255 2i = 510
511 i = 511 // 2 = 255 2i = 510 2i + 1 = 511

相当于2i是指向量维度中的偶数维,即第0维、第2维、第4维…,第510维,用sin函数计算;2i+1 是向量维度中的奇数维,即第1维、第3维、第5维..,第511维,用cos函数计算。

1.2 例子

假如要编码”我爱你”的位置向量,假定每个token都具备512维,如果位置下标从0开始时,则根据位置编码的计算公式可得

  • 当对上的单词”我”进行位置编码时,它本身的维度有512维

  • 当对上的单词”爱”进行位置编码时,它本身的维度有512维

然后再叠加上embedding向量,可得

  • 当对上的单词”你”进行位置编码时,它本身的维度有512维

最终得到的可视化效果如下图所示

1.3 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(dropout)

# 计算位置编码并将其存储在pe张量中
pe = torch.zeros(max_len, d_model) # 创建一个max_len x d_model的全零张量
position = torch.arange(0, max_len).unsqueeze(1) # 生成0到max_len-1的整数序列,并添加一个维度
# 计算div_term,用于缩放不同位置的正弦和余弦函数
div_term = torch.exp(torch.arange(0, d_model, 2) *
-(math.log(10000.0) / d_model))

# 使用正弦和余弦函数生成位置编码,对于d_model的偶数索引,使用正弦函数;对于奇数索引,使用余弦函数。
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # 在第一个维度添加一个维度,以便进行批处理
self.register_buffer('pe', pe) # 将位置编码张量注册为缓冲区,以便在不同设备之间传输模型时保持其状态

def forward(self, x):
# 将输入x与对应的位置编码相加
x = x + Variable(self.pe[:, :x.size(1)],
requires_grad=False)
return self.dropout(x)

代码中div_term如下:

其中的中括号对应的是一个从 0 到 的等差数列(步长为 2),设为i。

这里使用指数和对数运算的原因是为了确保数值稳定性和计算效率:

  • 一方面,直接使用幂运算可能会导致数值上溢或下溢。当较大时,中的幂可能会变得非常小,以至于在数值计算中产生下溢。通过将其转换为指数和对数运算,可以避免这种情况,因为这样可以在计算过程中保持更好的数值范围
  • 另一方面,在许多计算设备和库中,指数和对数运算的实现通常比幂运算更快。这主要是因为指数和对数运算在底层硬件和软件中有特定的优化实现,而幂运算通常需要计算更多的中间值

所以,使用指数和对数运算可以在保持数值稳定性的同时提高计算效率。

由于,从而有。于是上述公式与这个公式是等价的

由于以及,这表明位置的向量可以表示成位置和位置的向量组合,这提供了表达相对位置信息的可能性。

二、相对位置编码(Relative Positional Encoding)

2.1 简介

最先介绍相对位置编码的是论文《self-attention with relative positional representation》(RPR)。后面各种相对位置编码变体基本也是依葫芦画瓢的简单修改。

RNN中,第一个”I”与第二个”I”的输出表征不同,因为用于生成这两个单词的hidden states是不同的。对于第一个”I”,其hidden state是初始化的状态;对于第二个”I”,其hidden state是编码了”I think therefore”的hidden state。所以RNN的hidden state 保证了在同一个输入序列中,不同位置的同样的单词的output representation是不同的。

Transformer的位置编码如果只用绝对位置编码(absulate position encoding),与当前内容在原始句子中的相对位置是没有关系的。对比RNN系列的模型,Transformer的一个缺点是没有从网络结构上对位置信息进行处理,而只是把位置编码加入到了输入层。RPR的动机就是解决Transformer的这个天然缺陷,它的做法是把相对位置编码加入到了self-attention的内部。这样做的好处是当在计算权值或者特征值的时候,额外添加了位置信息,无疑将有助于这两个变量的计算。

RPR提出的模型的原理是在计算第个元素与第个元素之间的attention的值和权值的时候加入之间的距离编码,因为加入的是之间的相对位置关系,因此叫做相对位置编码。

例如对一个长度为5的序列,它共有9个相对位置编码信息(当前位置编码,当前位置的前4个,当前位置的后四个),如下表所示:

Index 解释 Value
3 位置i与位置i-1之间的距离 -1
4 位置i与位置i之间的距离 0
5 位置i与位置i+1之间的距离 1
6 位置i与位置i+2之间的距离 2
7 位置i与位置i+3之间的距离 3

通过加入上面的相对位置编码信息,再对比一下“I think therefore I am”中两个‘I’的输入有什么不同,如图第一个‘I’的相对位置编码信息:

第二个‘I’的相对位置编码信息:

RPR并没有根据输入序列的长度来确定需要考虑的相对位置之间的距离,而是用了一个固定的常数,也就是说我们需要学习的相对位置编码的序列长度为 。对于 的取值,论文中给出了不同值得对比实验结果,结论是当时,得到的效果非常接近。

2.2 实现

自注意力头对一个输入序列 进行操作,该序列有 个元素,其中 ,并计算一个长度相同的新序列 ,​​其中 。每个输出元素 都计算为线性变换的输入元素的加权和:

每个权重系数 都是使用 softmax 函数计算的:

并且 是使用比较两个输入元素的相似性函数计算的:

是参数矩阵。

RPR需要学习两个相对位置向量,一个是计算第个词特特征,另一个用于计算第个词到第个词之间的权值系数 ,不同于投影矩阵,这两个嵌入在注意力头之间是共享的。

RPR在self-attention中添加了两个可学习的变量。其中的计算方式改为:

改为:

这里用加法的原因是因为这样设计计算效率更高。

的计算方式相同,即在的范围之内计算相对距离,超出范围的用或者 进行截断:

然后学习相对位置表示 ,其中

相对位置将本来依赖于二元坐标的向量,改为只依赖于相对距离,并且通过截断,以适应不同任意的距离。这样一来,只需要有限个位置编码,就可以表达出任意长度的相对位置(因为进行了截断),不管是选择可训练式的还是三角函数式的,都可以达到处理任意长度文本的需求。

2.3 其他相对位置编码

可参考让研究人员绞尽脑汁的Transformer位置编码

三、Rotary Position Embedding(RoPE)

旋转式位置编码(Rotary Position Embedding,RoPE),这是一种配合Attention机制能达到“绝对位置编码的方式实现相对位置编码”的设计。而也正因为这种设计,它还是目前唯一一种可用于线性Attention的相对位置编码。

3.1 欧拉公式

3.1.1 复数

  1. 一般用表示复数,实数叫做复数的实部,实数叫做复数的虚部

  1. 复数的辐角是指复数在复平面上对应的向量和正向实数轴所成的有向角

  1. 的共轭复数定义为:,也可记作,复数与其共轭的乘积等于它的模的平方,即,这是一个实数

根据复数的定义:,可以看出来:,而这个展开过程就揭示了虚数背后的本质,因为这个展开过程中的两次乘法可以看成连续的操作:

  • 即把 1 经过2次完全一样的操作:,变成了 −1 ,那什么样的操作能得到这个效果呢?先旋转 90度,再旋转 90 度就可以了.如下图所示

所以,就代表了旋转:

  • 比如对于,自然数 1,绕坐标中心旋转180度(),再平移1 ,就回到坐标原点
  • 再比如对于

3.1.2 欧拉公式

表示任意实数,是自然对数的底数,是复数中的虚数单位,则根据欧拉公式有

表达的含义在于该指数函数可以表示为实部为,虚部为的一个复数

该欧拉公式相当于建立了指数函数、三角函数和复数之间的桥梁

  1. 由于有

  1. 所以,如果,则有

25的圆周运动来描述单位圆上的点,通过复平面的坐标来描述单位圆上的点,是同一个点不同的描述方式,所以有,如下图所示

可以推出:

把复数当作向量来看待,复数的实部是方向,虚部是方向,很容易观察出其几何意义,如下图所示

向量的加减法:

3.2 Rotary Position Embedding

在RoPE中,出发点就是“通过绝对位置编码的方式实现相对位置编码”,这样做既有理论上的优雅之处,也有实践上的实用之处,比如它可以拓展到线性Attention中就是主要因为这一点。为了达到这个目的,给self-attention中的向量都加入了位置信息后,便可以表示为

其中

  • 表示第个token对应的词向量集成位置之后的query向量
  • 表示第个token对应的词向量集成位置之后的 key 向量、 value 向量

然后使用$,_n,_n$来计算注意力权重,同时将输出计算为值表示的加权和。

Attention的核心运算是内积,所以我们希望的内积的结果带有相对位置信息,因此假设存在恒等关系:

该函数仅将词向量 及其相对位置 作为输入变量。

  • 左边算是向量的内积,而这恰好是transformer计算自注意力机制的核心一步,右边等式则意味着的相对位置如此一来,该等式便把“的内积”与“它们的相对位置”给串起来了
  • 左边是含有各自绝对位置信息的向量和向量,而这个等式就是RoPE追求的目标,物理含义就是通过显式传入绝对位置信息实现与传入相对位置信息对等的情况

假定现在词嵌入向量的维度是两维 ,然后RoPE利用2维度平面上的向量的几何性质,再结合复数的性质,找到了满足上述等式的,其形式如下:

这里面的表示复数的实部。

首先看第一个

这个式子的右边项有两部分,一部分是,一部分是

  1. 对于,可知其中的是个二维矩阵,是个二维向量,自然相乘的结果也必然是一个二维向量,用

  2. 对于 ,根据欧拉公式可得

  3. 由上述第1点结论,可知

然后将表示成复数形式(复数与平面直角坐标系上的点可以一一对应),可得

从而有

基于上面第2点结论,可知即是两个复数相乘

  1. 考虑到以下两个关于复数的背景知识

可得

将这个结果表达成实数向量形式,即是

至此,你也就不难发现,这不就是query向量乘以了一个旋转矩阵

至于第二个式子,根据上述过程同理,可得key向量

最后第三个式子

其中,表示一个复数的实数部分,而则表示复数的共轭

  1. 考虑到 再结合上面第一个式子中的推导,可得

结合可得

  1. 接下来证明上述函数的计算公式是成立的

首先,回顾一下attention操作,位置的query和位置的key会做一个内积操作即由

可得

相当于[A,B]与[C,D]做内积,则相当于A B横着,C D竖着,最终结果为AC BD,最后再把括号里的项全部对应相乘、展开

  1. 首先,把上面第二点的式子整理一下,总计8项,为了把相关的项提取出来,第1项 8项合并处理、第2项 7项合并处理、第3项 6项合并处理、第4项 5项合并处理

其次,考虑到

最后,再把相关项的特点,两次调整下顺序即可。依据以上三点,从而有

如此,也就证明了,位置的 query 和位置的 key 的内积就是函数

最后,把上面的式子一、式子二的最终结果都分别用矩阵向量乘的形式来表达就是:

接下来,我们要计算两个旋转矩阵的乘积,即中间部分的这个式子

展开之后,可得

从而有

上面都还只是针对词嵌入维度为2的情况,那对于的通用情况呢,将2维推广到任意维度,可以表示如下:

内积满足线性叠加性,因此任意偶数维的RoPE,我们都可以表示为二维情形的拼接,即将词嵌入向量元素按照两两一组分组

利用 的稀疏性, 相乘的更计算高效的实现是:

每组应用同样的旋转操作且每组的旋转角度计算方式如下:

所以简单来说 RoPE 的 self-attention 操作的流程是

  1. 对于 token 序列中的每个词嵌入向量,首先计算其对应的 query 和 key 向量
  2. 然后对每个 token 位置都计算对应的旋转位置编码
  3. 接着对每个 token 位置的 query 和 key 向量的元素按照 两两一组 应用旋转变换
  4. 最后再计算 query 和 key 之间的内积得到 self-attention 的计算结果

3.3 代码实现

3.3.1 非LLaMA版的实现

sinusoidal_position_embedding:这个函数用来生成正弦形状的位置编码。这种编码用来在序列中的令牌中添加关于相对或绝对位置的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def sinusoidal_position_embedding(batch_size, nums_head, max_len, output_dim, device):
# (max_len, 1)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(-1)

# (output_dim//2)
# 即公式里的i, i的范围是 [0,d/2]
ids = torch.arange(0, output_dim // 2, dtype=torch.float)
theta = torch.pow(10000, -2 * ids / output_dim)

# (max_len, output_dim//2)
# 即公式里的:pos / (10000^(2i/d))
embeddings = position * theta

# (max_len, output_dim//2, 2)
embeddings = torch.stack([torch.sin(embeddings), torch.cos(embeddings)], dim=-1)

# (bs, head, max_len, output_dim//2, 2)
# 在bs维度重复,其他维度都是1不重复
embeddings = embeddings.repeat((batch_size, nums_head, *([1] * len(embeddings.shape))))

# (bs, head, max_len, output_dim)
# reshape后就是:偶数sin, 奇数cos了
embeddings = torch.reshape(embeddings, (batch_size, nums_head, max_len, output_dim))
embeddings = embeddings.to(device)
return embeddings

为方便和transformer的位置编码做对比,故这里也假定output_dim = 512

  1. 首先,有 ids 张量,当 output_dim 为 512 时,则

ids = [0,0, 1,1, 2,2, …, 254,254, 255,255]

然后用一个基数为10000的指数运算,使用了公式 torch.pow(10000, -2 * ids / output_dim)

  1. 执行 embeddings = position * theta 这行代码,它会将 position 的每个元素与 theta 的相应元素相乘,前三个元素为

  1. 接下来我们将对 embeddings 的每个元素应用 torch.sin 和 torch.cos 函数

对于 torch.sin(embeddings),取 embeddings 中的每个元素的正弦值:

对于 torch.cos(embeddings),取 embeddings 中的每个元素的余弦值:

最后,torch.stack([torch.sin(embeddings), torch.cos(embeddings)], dim=-1) 将这两个新的张量沿着一个新的维度堆叠起来,得到的 embeddings如下

RoPE:这个函数将相对位置编码(RoPE)应用到注意力机制中的查询和键上。这样,模型就可以根据相对位置关注不同的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import torch
import torch.nn as nn
import torch.nn.functional as F
import math


def RoPE(q, k):
# q,k: (bs, head, max_len, output_dim)
batch_size = q.shape[0]
nums_head = q.shape[1]
max_len = q.shape[2]
output_dim = q.shape[-1]

# (bs, head, max_len, output_dim)
pos_emb = sinusoidal_position_embedding(batch_size, nums_head, max_len, output_dim, q.device)


# cos_pos,sin_pos: (bs, head, max_len, output_dim)
# 看rope公式可知,相邻cos,sin之间是相同的,所以复制一遍。如(1,2,3)变成(1,1,2,2,3,3)
cos_pos = pos_emb[..., 1::2].repeat_interleave(2, dim=-1) # 将奇数列信息抽取出来也就是cos 拿出来并复制
sin_pos = pos_emb[..., ::2].repeat_interleave(2, dim=-1) # 将偶数列信息抽取出来也就是sin 拿出来并复制

# q,k: (bs, head, max_len, output_dim)
q2 = torch.stack([-q[..., 1::2], q[..., ::2]], dim=-1)
q2 = q2.reshape(q.shape) # reshape后就是正负交替了

# 更新qw, *对应位置相乘
q = q * cos_pos + q2 * sin_pos

k2 = torch.stack([-k[..., 1::2], k[..., ::2]], dim=-1)
k2 = k2.reshape(k.shape)
# 更新kw, *对应位置相乘
k = k * cos_pos + k2 * sin_pos

return q, k
  1. sinusoidal_position_embedding函数生成位置嵌入。在output_dim=512的情况下,每个位置的嵌入会有512个维度,但为了简单起见,我们只考虑前8个维度,前4个维度为sin编码,后4个维度为cos编码。所以,可能得到类似以下的位置嵌入
1
2
3
4
# 注意,这只是一个简化的例子,真实的位置嵌入的值会有所不同。
pos_emb = torch.tensor([[[[0.0000, 0.8415, 0.9093, 0.1411, 1.0000, 0.5403, -0.4161, -0.9900],
[0.8415, 0.5403, 0.1411, -0.7568, 0.5403, -0.8415, -0.9900, -0.6536],
[0.9093, -0.4161, -0.8415, -0.9589, -0.4161, -0.9093, -0.6536, 0.2836]]]])
  1. 然后,提取出所有的sin位置编码和cos位置编码,并在最后一个维度上每个位置编码进行复制
1
2
sin_pos = pos_emb[..., ::2].repeat_interleave(2, dim=-1)  # 提取出所有sin编码,并在最后一个维度上复制
cos_pos = pos_emb[..., 1::2].repeat_interleave(2, dim=-1) # 提取出所有cos编码,并在最后一个维度上复制
  1. 更新query向量

首先构建一个新的q2向量,这个向量是由原来向量的负的cos部分和sin部分交替拼接而成的。用cos_pos对q进行元素级乘法,用sin_pos对q2进行元素级乘法,并将两者相加得到新的query向量

1
2
3
4
5
6
q2 = torch.stack([-q[..., 1::2], q[..., ::2]], dim=-1).flatten(start_dim=-2)
# q2: tensor([[[[-0.2, 0.1, -0.4, 0.3, -0.6, 0.5, -0.8, 0.7],
# [-1.0, 0.9, -1.2, 1.1, -1.4, 1.3, -1.6, 1.5],
# [-1.8, 1.7, -2.0, 1.9, -2.2, 2.1, -2.4, 2.3]]]])

q = q * cos_pos + q2 * sin_pos

公式如下

  1. ​​​​​更新key向量

对于key向量,处理方法与query向量类似

1
2
k2 = torch.stack([-k[..., 1::2], k[..., ::2]], dim=-1).flatten(start_dim=-2)
# k2: tensor([[[[-0.15, 0.05, -0.35, 0.25, -0.55, 0.45, -0.75, 0.65

attention的编码实现

attention:这是注意力机制的主要功能

  • 首先,如果use_RoPE被设置为True,它会应用RoPE,通过取查询和键的点积(并进行缩放)
  • 然后,进行softmax操作来计算注意力分数,以得到概率,输出是值的加权和,权重是计算出的概率
  • 最后,旋转后的计算点积注意力后,自然就具备了相对位置信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def attention(q, k, v, mask=None, dropout=None, use_RoPE=True):
# q.shape: (bs, head, seq_len, dk)
# k.shape: (bs, head, seq_len, dk)
# v.shape: (bs, head, seq_len, dk)

if use_RoPE:
# 使用RoPE进行位置编码
q, k = RoPE(q, k)

d_k = k.size()[-1]

# 计算注意力权重
# (bs, head, seq_len, seq_len)
att_logits = torch.matmul(q, k.transpose(-2, -1))
att_logits /= math.sqrt(d_k)

if mask is not None:
# 对权重进行mask,将为0的部分设为负无穷大
att_scores = att_logits.masked_fill(mask == 0, -1e-9)

# 对权重进行softmax归一化
# (bs, head, seq_len, seq_len)
att_scores = F.softmax(att_logits, dim=-1)

if dropout is not None:
# 对权重进行dropout
att_scores = dropout(att_scores)

# 注意力权重与值的加权求和
# (bs, head, seq_len, seq_len) * (bs, head, seq_len, dk) = (bs, head, seq_len, dk)
return torch.matmul(att_scores, v), att_scores


if __name__ == '__main__':
# (bs, head, seq_len, dk)
q = torch.randn((8, 12, 10, 32))
k = torch.randn((8, 12, 10, 32))
v = torch.randn((8, 12, 10, 32))

# 进行注意力计算
res, att_scores = attention(q, k, v, mask=None, dropout=None, use_RoPE=True)

# 输出结果的形状
# (bs, head, seq_len, dk), (bs, head, seq_len, seq_len)
print(res.shape, att_scores.shape)

3.3.2 LLaMA版的实现

接下来,再来看下LLaMA里是怎么实现这个旋转位置编码的,具体而言,LLaMA 的model.py文件里面实现了旋转位置编码

首先,逐一实现这三个函数precompute_freqs_cis,reshape_for_broadcast,apply_rotary_emb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 预计算频率和复数的函数
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) # 计算频率
t = torch.arange(end, device=freqs.device) # 根据结束位置生成序列
freqs = torch.outer(t, freqs).float() # 计算外积得到新的频率
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # 计算复数
return freqs_cis # 返回复数

# 重塑的函数
def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):
ndim = x.ndim # 获取输入张量的维度
assert 0 <= 1 < ndim # 检查维度的合理性
assert freqs_cis.shape == (x.shape[1], x.shape[-1]) # 检查复数的形状
shape = [d if i == 1 or i == ndim - 1 else 1 for i, d in enumerate(x.shape)] # 计算新的形状
return freqs_cis.view(*shape) # 重塑复数的形状并返回

# 应用旋转嵌入的函数
def apply_rotary_emb(
xq: torch.Tensor,
xk: torch.Tensor,
freqs_cis: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor]:
xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2)) # 将xq视为复数
xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2)) # 将xk视为复数
freqs_cis = reshape_for_broadcast(freqs_cis, xq_) # 重塑复数的形状
xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3) # 计算xq的输出
xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3) # 计算xk的输出
return xq_out.type_as(xq), xk_out.type_as(xk) # 返回xq和xk的输出

之后,在注意力机制的前向传播函数中调用上面实现的第三个函数 apply_rotary_emb,赋上位置信息

1
2
# 对Query和Key应用旋转嵌入
xq, xk = apply_rotary_emb(xq, xk, freqs_cis=freqs_cis)

四、ALiBi

ALiBi全称是Attention with Linear Biases,通过论文《Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation》提出,其不像标准transformer那样,在embedding层添加位置编码,而是在softmax的结果后添加一个静态的不可学习的偏置项(说白了,就是数值固定)

  1. 当计算每个头的注意力分数时,线性偏差注意力方法ALiBi会向每个注意力分数(,左)添加一个常数偏差(右)

左边是自注意力得分,关于的内积,右边是一个相对距离的矩阵,

所以才有

  • 之间的距离是0,所以对应位置就是0
  • 之间的距离是相对位置偏移为“k的索引”1 - q的索引,得到1-2 = -1,就对应到了中间矩阵的取值为-1了以此类推,相对距离矩阵的中间对角线上都是0,然后左下角的取值都是对应的k的索引-q的索引
  1. 具体怎么取值呢,按论文中的说法是

当8个heads的时候,的取值为:

如果是16个heads,则的取值为:

相当于追加了一半的到原来的8个head的每个的取值。

扩展到一般情况就是:对于个head的话,的取值就是

这样的个坡度了

最终整体的公式便是

对于第个query来说,他们之间的相对距离就是:的索引 - 的索引。具体而言,的索引遍历,而的索引 取值为

五、LLaMA 2 Long中位置编码的修改

LLaMA 2 Long论文《Effective Long-Context Scaling of Foundation Models》,LLaMA 2 Long相比LLaMA 2对位置编码进行了一个非常小的必要修改

在LLaMA 2中,它的位置编码采用的是旋转编码RoPE方法,其通过旋转矩阵来实现位置编码的外推

  1. 本质上来说,RoPE就是将表示单词、数字等信息的token embeddings映射到3D图表上,给出它们相对于其他token的位置——即使在旋转时也如此
  2. 这就能够使模型产生准确且有效的响应,并且比其他方法需要的信息更少,因此占用的计算存储也更小

然而,Meta的研究人员通过对70亿规模的LLaMA 2进行实验,确定了LLaMA 2中的RoPE方法的一个局限性,即,阻止注意力模块聚集远处token的信息。为此,Meta想出了一个非常简单的破解办法:

减少每个维度的旋转角度,具体而言就是将超参数“基频(base frequency)b”从10000增加到500000。Meta还通过可视化为螺旋图这一非常有趣的方式,将RoPE ABF与RoPE PI的差异进行了理论分析:

  • 上图b旨在说明位置插值对映射向量相对位置的影响,与上图a相比,连续点之间的距离被大幅缩小
  • 上图c说明了调整基频对结果的影响,虽然螺旋频率增加导致点之间最小距离缩小,但连续点之间的距离几乎与上图a相同,即螺旋频率增加所带来的影响将在高频段中逐渐减少

总之,与RoPE PI相比,RoPE ABF的优势主要体现在它能以更大的粒度分配嵌入向量(the embedded vectors),从而使模型更容易区分位置。此外,他们还观察到,嵌入向量之间的相对距离既对RoPE PI的关键参数有线性依赖性,也对RoPE ABF的关键参数也有对数依赖性。这也就是为什么可以很容易地对基频这一超参数“下手”。

缩小了RoPE对远端token的衰减效应,并且在扩展LLAMA的上下文长度上优于一项类似的名为“位置插值”的方法RoPE PI(如下图所示,RoPE表示基线方法,RoPE ABF为Meta此次发明的新方法,xPos是另一种应用了该方法的旋转编码变体)

LLaMA 2 Long凭借着这一改动,达成了3.2万的上下文token,并通过长下文连续预训练的共同作用,获得了开头所示的好成绩。

参考

  1. 一文通透位置编码:从标准位置编码、旋转位置编码RoPE到ALiBi、LLaMA 2 Long
  2. Effective Long-Context Scaling of Foundation Models
  3. TRAIN SHORT, TEST LONG: ATTENTION WITH LINEAR BIASES ENABLES INPUT LENGTH EXTRAPOLATION
  4. Transformer升级之路:2、博采众长的旋转式位置编码
  5. ROFORMER: ENHANCED TRANSFORMER WITH ROTARY POSITION EMBEDDING
  6. Transformer升级之路:18、RoPE的底数设计原则
  7. 详解Transformer-XL
  8. 【NLP】浅谈 Transformer-based 模型中的位置表示
  9. [NLP] 相对位置编码(一) Relative Position Representatitons (RPR) - Transformer
  10. [NLP] 相对位置编码(二) Relative Positional Encodings - Transformer-XL
  11. How Self-Attention with Relative Position Representationsworks
  12. 让研究人员绞尽脑汁的Transformer位置编码
  13. POSITION INFORMATION IN TRANSFORMERS: AN OVERVIEW
  14. Self-Attention with Relative Position Representations
  15. The Large Language Model Playbook
  16. RoPE与多头注意力: 从零开始实现llama3系列2
  17. Attention Is All You Need
  18. DeBERTa: Decoding-enhanced BERT with Disentangled Attention
  19. Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer
  20. Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context

LLM(六)——Position Encoding
https://mztchaoqun.com.cn/posts/D44_Position-Encoding/
作者
mztchaoqun
发布于
2024年10月27日
许可协议