青岛网站建设公司正,关键词爱站网,影院资讯 wordpress,北京市建设局网站旋转位置编码#xff08;Rotary Position Embedding#xff0c;RoPE#xff09;是一种能够将相对位置信息依赖集成Attention计算里的方法。就是在做词表映射的时候不是单一的进行一个embedding计算#xff0c;还考虑位置信息。 一些资料
[1] https://arxiv.org/pdf/2104.0…旋转位置编码Rotary Position EmbeddingRoPE是一种能够将相对位置信息依赖集成Attention计算里的方法。就是在做词表映射的时候不是单一的进行一个embedding计算还考虑位置信息。 一些资料
[1] https://arxiv.org/pdf/2104.09864
[2] https://arxiv.org/pdf/1706.03762
https://colab.research.google.com/drive/1rPk3ohrmVclqhH7uQ7qys4oznDdAhpzF
LLM Visualization
neural networks - What exactly are keys, queries, and values in attention mechanisms? - Cross Validated
Attention
从论文里: An attention function can be described as mapping a query and a set of key-value pairs to an output. Attention即把查询向量映射成一个输出的操作。在看之前先看下embedding和qkv等几个基本概念。
Embedding
embdding的作用是词表到特征向量的映射即把一个int的index索引映射到一个向量空间表示。比如看llama的token: https://huggingface.co/meta-llama/Llama-2-7b-hf/blob/main/tokenizer.json
截图: vocab里面是当前模型的所有词表比如unk对应的索引是0。通过tokenizer.endocer的编码操
作就可以将一个字段编译成一组index vector。比如:
from transformers import LlamaForCausalLM, LlamaTokenizertokenizer LlamaTokenizer.from_pretrained(/model/Llama-2-7b-chat-hf)encode tokenizer.encode(who)
print(encode)# [1, 1058]
# - 1
# who - 1058
然后得到的[1, 1058]就是embedding的输入了。embeding的计算: import torcha torch.tensor([1]) # 输入index是1就是前面encode后的数值 import torch.nn as nnembedding nn.Embedding(3, 3)embedding.weight
Parameter containing:
tensor([[ 0.0198, -0.5562, 0.8156],[-0.3192, -1.2203, -0.8307],[-0.1649, -0.2753, -0.9075]], requires_gradTrue)o embedding(a) # 从weight里取出来对应的第1行的数值。o
tensor([[-0.3192, -1.2203, -0.8307]], grad_fnEmbeddingBackward0)
embeeding的操作是根据输入的index的数值(比如这里1)然后从对应的权重(embedding.weight, 3x3)里取出对应index行索引对应的权重vector(第一行embedding.weight[1])。即一个简单的索引操作。所以这里就有一个限制即输入的index的大小必须要小于embedding.weight的shape(0)超出了就会挂掉。另外embedding.weight的shape(1)的大小被称为hidden size即feature的大小。后面的linear等操作都是根据这个feature来运算的。实际上embedding将index变成了一个高维空间的表示可以用于模型训练推理。正常下embedding的权重第一个维度大小是vocab_size可以查看config.json配置文件。 query、keys and values
query: 希望取查询的文本对应的查找向量即LLM的输入经过embedding映射后的向量就是一个query vector。
keys: 和query是对输入的文本的一个向量映射。keys里存储了所有的之前LLM推理后的context的vector数值。The key vectors are used to compute how relevant each element in the input sequence is to the query.
values: values是和query一一对应的一个key对应一个values, These values are weighted by the attention scores (computed from the query-key interaction) to determine how much each element contributes to the final output.
也就是根据key和value来判断当前的query和哪个key的关联性最高分数最高。那么是怎么计算的呢参考qkv比较清楚计算方式了。 从图中可以看到对一个query([1, 0, 2])会分别和三组key/value计算得到三组vactor。然后再把三组vector相加得到[2, 7, 1.5]这个vector在经过重复的mlp和attention和最后的logits处理得到llm模型的输出index, 比如 第一次输入How to predict, 经过llm根据最后的softmax后vector选取最优的输出。每个有个score. prefill的时候全部是query没有key value的decode的时候query长度都是1然后之前的context存储在所有的key value里。
Attention is all you need
An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. attention表示: 左图是最简单的attention操作右边是multi head attention的图. 先看左图输入QKV经过计算Scaled Dot Product Attention得到一组特征向量然后每一组query和key/value得到一个vector(如上面qkv章节的图),然后将vector相加。计算公式 :
scaled dot product attention: query和key的维度是。如qkv章节的图q和k点乘然后经过softmax之后得到一个score分数。在将这个score和V做乘法即得到一个基于当前组k/v的输出特征向量。
一个scaled dot product的pytorch实现
class ScaledDotProductAttention(nn.Module): Scaled Dot-Product Attention def __init__(self, temperature, attn_dropout0.1):super().__init__()self.temperature temperatureself.dropout nn.Dropout(attn_dropout)def forward(self, q, k, v, maskNone):attn torch.matmul(q / self.temperature, k.transpose(2, 3))if mask is not None:attn attn.masked_fill(mask 0, -1e9)attn self.dropout(F.softmax(attn, dim-1))output torch.matmul(attn, v)return output, attn
Multi-Head AttentionMHA
多头注意力机制和单个scaled dot product attention的区别是先将输入的QKV分别进行多个linear操作然后在最后的输出vector也经过多个linear映射。这样可以将qkv的向量映射到更高维度的空间上。每个linear的输出大小可以表示为head_dim, 有多少和linear可以用head_num来表示。多头(head即指这里head_num有几个。一般在实现的时候由于linear是线性的所以可以将多个head合并成一个linear算子来实现这个linear的权重大小就是head_num * head_dim这样也自动实现了图里面的concat操作。 一个MHA的实现:
class MultiHeadAttention(nn.Module): Multi-Head Attention module def __init__(self, n_head, d_model, d_k, d_v, dropout0.1):super().__init__()self.n_head n_headself.d_k d_kself.d_v d_vself.w_qs nn.Linear(d_model, n_head * d_k, biasFalse)self.w_ks nn.Linear(d_model, n_head * d_k, biasFalse)self.w_vs nn.Linear(d_model, n_head * d_v, biasFalse)self.fc nn.Linear(n_head * d_v, d_model, biasFalse)self.attention ScaledDotProductAttention(temperatured_k ** 0.5)self.dropout nn.Dropout(dropout)self.layer_norm nn.LayerNorm(d_model, eps1e-6)def forward(self, q, k, v, maskNone):d_k, d_v, n_head self.d_k, self.d_v, self.n_headsz_b, len_q, len_k, len_v q.size(0), q.size(1), k.size(1), v.size(1)residual q# Pass through the pre-attention projection: b x lq x (n*dv)# Separate different heads: b x lq x n x dvq self.w_qs(q).view(sz_b, len_q, n_head, d_k)k self.w_ks(k).view(sz_b, len_k, n_head, d_k)v self.w_vs(v).view(sz_b, len_v, n_head, d_v)# Transpose for attention dot product: b x n x lq x dvq, k, v q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)if mask is not None:mask mask.unsqueeze(1) # For head axis broadcasting.q, attn self.attention(q, k, v, maskmask)# Transpose to move the head dimension back: b x lq x n x dv# Combine the last two dimensions to concatenate all the heads together: b x lq x (n*dv)q q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)q self.dropout(self.fc(q))q residualq self.layer_norm(q)return q, attn
multi head的体现就体现在了qkv三个linear上一般的权重大小是:
x: [bs, seq_len, hidden_size] # hidden size有embedding的输出决定
Q: [hidden_size, num_hididden_heads * head_dim] # Q有num_hiddden_heads个头每个head大小是head dim.
K/V: [hidden_size, num_key_value_heads * head_dim] # KV的head num一样
查看每个LLM模型的head_num等参数可以看对应权重目录下的config.json
{_name_or_path: meta-llama/Llama-2-7b-hf,architectures: [LlamaForCausalLM],bos_token_id: 1,eos_token_id: 2,hidden_act: silu,hidden_size: 4096,initializer_range: 0.02,intermediate_size: 11008,max_position_embeddings: 4096,model_type: llama,num_attention_heads: 32,num_hidden_layers: 32,num_key_value_heads: 32,pretraining_tp: 1,rms_norm_eps: 1e-05,rope_scaling: null,tie_word_embeddings: false,torch_dtype: float16,transformers_version: 4.31.0.dev0,use_cache: true,vocab_size: 32000
} RoPE
Positional Encoding
位置编码主要是将位置信息加入到输入vector里。啥意思为啥呢还是看qkv章节里的图attention的原理是计算q和每个kv的关系向量假设三个kv则得到三个vector然后将三个vector做加法。这个操作就导致如果将三组kv的顺序换掉的话还是得到一样的三个vector, 做加法后输出还是一样的, 自注意机制无法捕捉位置信息比如输入是I am a和a I am的attention特征向量一样了那结果肯定有问题的。所以希望在计算attention的时候除了vector之外还可以带上当前vector的位置信息。这个工作就用positionl encoding来完成。
最简单的想法是用绝对位置编码即对每一个embedding后的vector加上一个独立的position encoding向量(比如one hot的向量1表示位置信息其他为0或者根据论文里的实现通过下面方式正弦编码Sinusoidal:
import torch
import torch.nn as nn
import numpy as npclass PositionalEncoding(nn.Module):def __init__(self, d_hid, n_position5):super(PositionalEncoding, self).__init__()# Not a parameterself.register_buffer(pos_table, self._get_sinusoid_encoding_table(n_position, d_hid))def _get_sinusoid_encoding_table(self, n_position, d_hid): Sinusoid position encoding table # TODO: make it with torch instead of numpydef get_position_angle_vec(position):return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]sinusoid_table np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])sinusoid_table[:, 0::2] np.sin(sinusoid_table[:, 0::2]) # dim 2isinusoid_table[:, 1::2] np.cos(sinusoid_table[:, 1::2]) # dim 2i1return torch.FloatTensor(sinusoid_table).unsqueeze(0)def forward(self, x):return x self.pos_table[:, :x.size(1)].clone().detach()model PositionalEncoding(10)
x torch.randn([1, 10])model(x)
输出一下sinusoid_table:
0 10 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
1 10 [1.0, 1.0, 0.15848931924611134, 0.15848931924611134, 0.025118864315095794, 0.025118864315095794, 0.003981071705534973, 0.003981071705534973, 0.000630957344480193, 0.000630957344480193]
2 10 [2.0, 2.0, 0.3169786384922227, 0.3169786384922227, 0.05023772863019159, 0.05023772863019159, 0.007962143411069947, 0.007962143411069947, 0.001261914688960386, 0.001261914688960386]
3 10 [3.0, 3.0, 0.47546795773833406, 0.47546795773833406, 0.07535659294528739, 0.07535659294528739, 0.011943215116604919, 0.011943215116604919, 0.0018928720334405792, 0.0018928720334405792]
4 10 [4.0, 4.0, 0.6339572769844454, 0.6339572769844454, 0.10047545726038318, 0.10047545726038318, 0.015924286822139894, 0.015924286822139894, 0.002523829377920772, 0.002523829377920772]
论文里的公式: 每一行position是不一样的然后对每个0hidden size上偶数位置用sin计算pe奇数位置用cos计算pe值。这种计算的好处是不仅将position值考虑进计算而且相邻的区域使用sin/cos进行了区分而且由于sin/cos是有边界的函数这样也导致pe的值不会太大。
位置编码的主要目的就是将位置信息引入到embedding后的特征向量表示中将每个kv的vector在空间表示的时候可以完全的分开。
但是相对位置编码有个问题就是和训练时候的vocab size强绑定当推理词表长度和训练不一样那相对位置编码就没有这么强的泛化性了。这个被称为外推性外推性是指大模型在训练时和预测时的输入长度不一致导致模型的泛化能力下降的问题。而且绝对位置编码还是只和当前的位置有关系无法感知相对位置信息。
RoPE
Rotary Position EmbeddingRoPE选择位置编码考虑了相对位置信息使用旋转矩阵来实现。什么是旋转矩阵, 在计算机视觉里旋转矩阵常用来变换图像像素: R是一个旋转矩阵可以将像素点(x, y)进行一定的旋转变换。在rope论文里就使用了旋转矩阵来变换embedding词向量: 然后根据2维度的旋转矩阵就得到了rope层的大的旋转矩阵。然后经过计算和推导后..(todo...)