【导读】:本文是LLM知识点第四篇,介绍LLM中的最重要的Attention机制,具体有Attention机制,Self-Attention,Multi-head Attention的原理和实现细节,接着会介绍 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 12px;letter-spacing: normal;text-align: left;text-wrap: wrap;background-color: rgb(255, 255, 255);outline: 0px;visibility: visible;">围绕KV Cache进行的推理优化而提出的 MQA,GQA和MLA。
Attention机制
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【1】简要介绍Attention机制提出Attention的论文 : Attention Is All You Need 论文地址:https://arxiv.org/pdf/1706.03762.pdf
提出Attention的背景:RNN处理序列数据时,token是逐个喂给模型的。比如在a3的位置,模型要等a1和a2的信息都处理完成后,才可以生成a3。存在问题是:a.随着序列长度的增加,模型并行计算的能力变差。b.随着token间距离的增加,对于远距离处的信息,RNN很难捕获其依赖关系。 针对问题改进:提升模型的并行运算能力,序列中的每个token能无损地捕获序列里的其他tokens信息。改进办法就是Attention。 如蓝色方框为attention模型。在每个位置,例如在a2处产生b2时,attention将会同时看过a1到a4的每个token。此外,每个token生成其对应的输出的过程是同时进行的,计算不需要等待。
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 12px;font-weight: 700;letter-spacing: 0.408px;text-align: left;text-wrap: wrap;width: 333px;"/>
【推荐阅读】Transformer学习笔记二:Self-Attention(自注意力机制)-猛猿【202111】
Attention是什么?:Attention机制是Transformer架构引入的提取信息的方法。Attention机制通过对模型的输入部分 赋予不同的权重,对value值进行加权求和。以此来抽取数据中更重要的信息。 Attention机制的核心是从关注全部到关注重点。Attention机制本质上是对源数据中元素的值(value)进行加权求和,而其中查询(query)和键(key)用于计算权重系数。 Attention函数的本质 : Attention函数可以描述为将一个查询(query)映射到一系列键值对(key-value)的过程,其中通过计算查询(query)和键(key)的相似性或相关性来得到每个键对应值的权重系数,最终对值(value)进行加权求和,以产生Attention数值。
Attention的优点: 1.相对于传统的CNN和RNN,Attention模型的参数数量更少。2.使得Transformer模型在计算Query时实现并行计算。3.使得模型能够更好地捕捉长距离的依赖关系,模型效果更好。
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【2】Attention机制的计算流程【带权求和】 Attention机制计算流程: 1.计算Query和各个Key的相似度(点乘),得到每个Key对应Value 的权重系数s;
2.对权重系数s 进行softmax 归一化,得到直接可用的权重 a。 3.对权重系数 a 和权值Value 进行加权求和,得到最终的attention数值 c。 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-align: left;text-wrap: wrap;background-color: rgb(255, 255, 255);width: 1072.22px;"/>
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【3】以机器翻译为例理解Attention过程以李宏毅深度学习授课资料:Attention-based Model所讲述的机器翻译为例。注:传统Attention模块能捕获source端和target端的token间的依赖关系。
目标:将‘机器学习’翻译为‘machine learning’。attention其实就是一个当前的输入与输出的匹配度,即为h1和z0的匹配度。h1为当前时刻RNN的隐层输出向量,而不是原始输入的词向量,z0初始化向量,如rnn中的初始记忆。
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;letter-spacing: normal;text-align: start;text-wrap: wrap;background-color: rgb(255, 255, 255);margin-bottom: 8px;margin-top: 8px;">第一步:求z与h的相似性得到a;第二步:softmax归一化处理得到概率值a^;第三步:对h加权求和得c;
【推荐阅读】https://www.jianshu.com/p/d7f50cc5560e
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【4】Attention的机制的代码实现注意力机制的代码实现
importtorch importtorch.nnasnn fromtypingimportList defget_input_embeddings(words ist[str],embeddings_dim:int): #wearecreatingrandomvectorofembeddings_dimsizeforeachwords #normallywetrainatokenizertogettheembeddings. #checktheblogontokenizertolearnaboutthispart embeddings=[torch.randn(embeddings_dim)forwordinwords] returnembeddings text="Ishouldsleepnow" words=text.split("") len(words)#4 embeddings_dim=512#512dimbecausetheoriginalpaperusesit.wecanuseotherdimalso embeddings=get_input_embeddings(words,embeddings_dim=embeddings_dim) embeddings[0].shape#torch.Size([512]) #initializethequery,keyandvaluemetrices query_matrix=nn.Linear(embeddings_dim,embeddings_dim) key_matrix=nn.Linear(embeddings_dim,embeddings_dim) value_matrix=nn.Linear(embeddings_dim,embeddings_dim) query_matrix.weight.shape,key_matrix.weight.shape,value_matrix.weight.shape#torch.Size([512,512]),torch.Size([512,512]),torch.Size([512,512]) #query,keyandvaluevectorscomputationforeachwordsembeddings query_vectors=torch.stack([query_matrix(embedding)forembeddinginembeddings]) key_vectors=torch.stack([key_matrix(embedding)forembeddinginembeddings]) value_vectors=torch.stack([value_matrix(embedding)forembeddinginembeddings]) query_vectors.shape,key_vectors.shape,value_vectors.shape#torch.Size([4,512]),torch.Size([4,512]),torch.Size([4,512]) #computethescore scores=torch.matmul(query_vectors,key_vectors.transpose(-2,-1))/torch.sqrt(torch.tensor(embeddings_dim,dtype=torch.float32)) scores.shape#torch.Size([4,4]) #computetheattentionweightsforeachofthewordswiththeotherwords softmax=nn.Softmax(dim=-1) attention_weights=softmax(scores) attention_weights.shape#torch.Size([4,4]) #attentionoutput output=torch.matmul(attention_weights,value_vectors) output.shape#torch.Size([4,512])
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【5】Attention的应用场景
图片来自https://mp.weixin.qq.com/s/OabP1Apl5ullK4oxiyfqJA
Self-Attention
ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;">【1】简要介绍Self-AttentionSelf-Attention提出论文:《Attention Is All You Need》 提出Self-Attention的原因:传统Attention模块能捕获source端和target端的token间的依赖关系,但不能捕获source端或target端自身token间的依赖关系。提出self-attention可以学习source端句子内部的token间的依赖关系,捕获句子的内部信息。 在一般任务的Encoder-Decoder框架中,输入 Source 和输出 Target 内容是不一样的,比如对于英 - 中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子。
Self-Attention的简介:在Self-Attention中,是在输入序列内部做Attention,输入序列中的每个token要和该序列中其余每个token进行Attention计算。这样可以学习句子内部的词(token)间的依赖关系,捕获句子的内部信息。Self-Attention机制是Attention机制的变体,其减少了对外部信息的依赖,更擅长捕捉数据或特征的内部相关性。 Attention和Self-Attention的区别: Self-Attention多两个约束条件 : 1. Q,K,V的计算输入是同源的,K-Q-V三者都来源于 X 。 2. Q,K,V需要遵循attention的做法。 Self的意思就是就是Attention完全来自输入序列自己,而不来自外部信息(比如output)。
Encoder#1 ,输入为 词向量矩阵X ,在计算attention时,有Q=XWQ , K=XWK, V=XWV,故这里是self-attention。 Dncoder#1 ,输入为词向量矩阵X'和C,在计算第二个attention时,有Q=CWQ, K=X'WK, V=X'WV,故这里是cross-attention。Q的计算使用Encoder#2的输出C,与X'的值不同源。
Self-Attention的计算方式:
Self-attention的意思是,给Attention的输入都来自同一个序列,其计算方式如下:
self-attention计算框架
【2】Self-Attention的计算公式, 参数量 Self-Attention的计算公式(QKV计算不加偏置项): 缩放点积注意力:Scaled Dot-Product Attention
注:
X的维度(seq_length,d_model )=(单词个数,词向量维度 );
WQ=WK 维度(hidden_size ,hidden_size );
WV维度(hidden_size ,hidden_size ); 可设置
Q, K, V 维度(seq_length,hidden_size );
QKᐪ维度(seq_length,seq_length );
hidden_size=d_model//num_head,这里num_head=1,有hidden_size=d_model
根据上面的公式,直觉上会觉得attention只有3个矩阵。但是transformer中使用的是Multi-Head Attention,会多出一个输出权重矩阵,变成4个矩阵。(划重点)为什么多出一个输出权重矩阵?请看原论文中的图解,各个head的结果concat后会再过一个linear层,这个linear层就是输出权重矩阵。 Self-Attention的参数量:
上面是一个简化的算法,multi-head attention也符合这个参数量,但实际上需要分head计算再组合。
附Attention(Q,K,V)计算图解:
注意:此处得到矩阵Z,第1行代表x1的注意力得分。
【3】Self-Attention的计算过程和图解 【词向量矩阵X】Self-Attention的计算过程图解
a.Query矩阵,Key矩阵,Value矩阵的计算
b.最后计算得分和注意力输出
说明:
a.这里矩阵X计算self-attention,Q,K,V计算同源都使用词向量矩阵X;
b.对于每个词向量xi,计算得到Q,K,V后,都有Qi,Ki,Vi与之对应。
Self-Attention的计算过程详细步骤:
1.将词向量矩阵X与权重矩阵 相乘,得到Q,K,V矩阵。
2.将Q和K相乘得到权重Score矩阵,除以根号dk并softmax得到新的权重score矩阵 。
3.将权重score矩阵 和该序列的V矩阵相乘,得到该序列对应的Attention得分矩阵 。
【3】Self-Attention的计算过程和图解2 【词向量矩阵X】Self-Attention的计算过程图解2
a.Query矩阵,Key矩阵,Value矩阵的计算
b.最后计算得分和注意力输出
【引用链接】Transformer学习笔记二:Self-Attention(自注意力机制)-猛猿【202111】
【4】Self-Attention的计算过程和图解
【词向量x】Self-Attention的计算过程图解:
说明:
a.对于每个词向量xi,计算得到Q,K,V后,都有qi,ki,vi与之对应。
b.每个词向量xi,其Attention-score计算结果为zi。zi的维度为(1,seq_length)。
c.对于单个词向量x1计算步骤,q1要和 K矩阵 相乘是q1Kᐪ,经scaled和softmax得到 权重矩阵 a,
将权重矩阵a1和 V矩阵相乘是 a1· V= z1 。
某个单词Zi 的计算方法: softmax后的score矩阵 的第1行表示单词x1与其他所有单词的attention系数。 最终单词 x1的输出 z1 等于所有单词i 的值??根据 attention 系数的比例相加得到,如下图所示:
d.注意看QKᐪ, 其实是个word2word的attention map!(加了softmax之后就是一个和为1的权重了)。
比如输入序列是 "i have a dream" 总共4个单词,这里就会形成4x4的注意力机制的图:
注意encoder里面是叫self-attention,decoder里面是叫masked self-attention。
mask就是沿着对角线把灰色的区域用0覆盖掉,不给模型看到未来的信息。
masked self-attention就是只能看见当前位置和之前token的信息。如a作为第三个单词,有和i,have,a 三个单词的attention。
【5】计算Self-Attention是为什么要除以d_k的开方? Transformer计算 self-attention的计算QKᐪ后 为什么要除以 d_k的开方?【重点】
计算QKᐪ后除以 d_k的开方是 为了压缩softmax的输入值,使得输入值分布变得更好,能够进入softmax的敏感区间,这样在训练的时候不至于梯度值更新的太小,能够防止梯度消失问题。如果不进行scaleing,模型训练时将很难收敛。
【dk就是词向量维度/隐藏层维度】
论文中作者的解释是发现当维度dk值很大时,输入softmax的值QKᐪ就越大,会导致后面的softmax计算会有极小的梯度,不利于更新学习,因此除以dk,防止梯度消失。(softmax值过大,其偏导数趋于0)
Wesuspectthatforlargevaluesofdk,thedotproductsgrowlargeinmagnitude,pushingthesoftmaxfunctionintoregionswhereithasextremelysmallgradients.
计算QKᐪ后 为什么scaled参数选择除根号d_k?
选择根号d_k是因为可以使得QKᐪ 的结果满足期望为0,方差为1的分布,类似于归一化。于此同时,除以根号d_k使得输入值进入softmax的敏感区间,能够防止梯度消失问题。
计算QKᐪ后 有其他方法不用除根号dk吗?
有,只要能做到每层参数的梯度保持在训练敏感的范围内,使得网络比较好训练。能缓解梯度消失的问题就可以。那么这个网络就比较好训练。不用除根号dk方式有,详情可以了解Google T5的Xavier初始化。
【6】为什么Q和K要使用不同的权重矩阵进行线性变换投影? Self-Attention计算公式:
为什么要计算Q和K的点乘?
简单的说,Q和K的点乘是为了计算序列中每个token与其他token的相似度,最终得到attention score 矩阵,用来对V进行提纯。 假设一个句子"Hello, how are you?"长度是6,embedding维度是300,那么Q,K,V都是(6, 300)的矩阵。比如说"Hello, how are you?"这句话,当前token为”you"的时候,可以知道”you“对于"Hello",” , “, "how","are","?"这几个token对应的关注度是多少。有了这个attention score,可以知道处理到”you“的时候,模型在关注句子中的哪些token。
计算Self-Attention为什么Q和K要使用不同的权重矩阵进行线性变换投影? 如果WQ和WK一样,则QKᐪ结果是对称矩阵,这样就减弱了模型的表达能力。同时在对称矩阵中,对角线的值会比较大,导致每个token过分关注自己。使用不同的投影矩阵,参数增多,可以增强模型表达能力。
为什么要使用Q,K,V,仅仅使用QV,KV或者V为什么不行?
使用QKV主要为了增强网络的容量和表达能力。self-attention使用Q,K,V这三个参数独立,模型的表达能力和灵活性显然会比只用QV或者只用V要好些,
【7】Self-Attention实现的细节问题 Self-Attention的计算公式(QKV计算不加偏置项):
缩放点积注意力:Scaled Dot-Product Attention
Self-Attention的计算公式(QKV计算加偏置项):
Self-Attention计算时W Q. W K. W V 的由来? 先初始化为[h,h]维度,再去用模型训练学习这里面的参数。 Self-Attention计算时乘上W Q. W K. W V 的好处?
1.增加了参数量,增加模型的表达能力。
2.加入了不同的线性变换相当于对x 做了不同的投影,将向量x 投影到不同空间,增加模型的泛化能力。
3.允许某个token对其他位置token的注意力大于对自己的注意力,才能更好的捕捉全局位置的注意力。
【引用链接】:https://zhuanlan.zhihu.com/p/626820422
Self-Attention 的时间复杂度是怎么计算的?
Self-Attention时间复杂度:O(n²⋅d),这里,n是序列的长度seq_length ,d是embedding的维度d_model。
Self-Attention包括三个步骤:相似度计算,softmax和加权平均,时间复杂度分别是:
相似度计算可看作为(n,d)和(d,n)的矩阵相乘 n,d)∗(d,n)=O(n²⋅d),
softmax就是直接计算了,时间复杂度为O(n²)
加权平均可看作为(n,n)和(n,d)的矩阵相乘 n,n)∗(n,d)=O(n²⋅d)
Self-Attention的时间复杂度是 O(n²⋅d)。
【引用链接】:https://zhuanlan.zhihu.com/p/132554155
Self-Attention和Multi-head Attention的参数量怎么计算?
self-attention块的模型参数有Q,K,V的权重矩阵WQ,WK,WV和偏置,输出权重矩阵Wo和偏置。
4个权重矩阵的形状为【h,h】,4个偏置的形状为【h】。总参数量为4h²+4h.
Multi-head Attention也符合这个参数量,但实际上需要分head计算再组合。
Self-Attention是如何解决长距离依赖问题的呢?
解决方式:利用注意力机制来“动态”地生成不同连接的权重,从而处理变长的信息序列。
具体介绍:对于当前query,需要与句子中所有 key 进行点乘后再 Softmax ,以获得句子中所有 key 对于当前query的score(可以理解为权重),然后与所有词的value向量进行加权融合之后,就能使当前token学习到句子中其他词的信息;
Transformer 中self-attention是如何并行化的?
Transformer 的并行化主要体现在 self-attention 模块,在Encoder端 Transformer可以并行处理整个序列,并得到整个输入序列经过 Encoder 端的输出,在 self-attention 模块,对于某个序列(x1,x2,...xn),self-attention 模块可以直接计算xi,xj的点乘结果,而RNN系列的模型就必须按照顺序从x1计算到xn。
在Self-Attention能够并行的计算句子中不同的query,每个query之间并不存在先后依赖关系,使得transformer能够并行化;
Self-Attention 在计算的过程中,如何对padding位做mask?
【参考链接】:https://zhuanlan.zhihu.com/p/149634836
Attention与全连接层的区别何在? 既然Attention是为了关注某些局部信息,那些不就相当于连上一层在关注的部分权重更大的全连接层吗,二者的区别何在?正如你所说的,Attention的最终输出可以看成是一个“在关注部分权重更大的全连接层”。但是它与全连接层的区别在于,注意力机制可以利用输入的特征信息来确定哪些部分更重要。
Transformer模型中,注意力计算后面使用了两个FFN层,为什么第一个FFN层先把维提升,第二个FFN层再把维度降回原大小? 1、提升维度:类似SVM kernel,通过提升维度可以识别一些在低维无法识别的特征。 3、降回原维度:方便多层注意力层和残差模块进行拼接,而无需进行额外的处理。 https://mp.weixin.qq.com/s/DXOKLkXdTFfANpvV4eiQNA
SelfAttention的一些反思【重要】 深入思考,会发现它真的是一个很神奇的存在,它是BERT乃至整个预训练语言模型的基石,是接棒CNN/RNN,成为特征抽取的新利器。Attention is all you need !
0、深度学习中Attention与全连接层的区别何在?[15]
注:这是一个检验你是否真正理解Attention的问题
1、self-attention 的本质是什么?包括哪几个步骤?和普通 Attention 的差别在哪里?[4]
2、不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵,会有什么问题?[4]
3、在普通 attention 中,一般有 k=v,那 self-attention 可以嘛?[4]
4、self-attention 在计算的过程中,如何对padding位做mask?[2]
5、bert的mask为何不学习transformer在attention处进行屏蔽score的技巧?[11]
6、XLNet为什么不直接在attention掩码矩阵中只把当前的单词掩盖住来获取上下文的信息呢?直接mask住左上到右下的对角线构建双向语言模型不行吗?[3]
Multi-head Attention 【1】MHA提出的背景 MHA提出论文:《Attention Is All You Need》
MHA实现思路:简单来说,MHA在d_model维度进行拆分,将原来单个self-attention的计算拆分成h个小份self-attention分别计算,最后将计算结果合并和变换回原来的维度。h是head的个数。
所谓Multi-Head Attention其实是把QKV的计算并行化,原始attention计算d_model维的向量,而Multi-Head Attention则是将d_model维向量先经过一个Linear Layer,再分解为h个Head计算(d_model/h维)attention,最终将这些attention计算结果连在一起后再经过一层Linear Layer输出。[配合图解1理解]
MHA简要介绍:为了提高模型的表达能力,Transformer引入了多头注意力机制,允许模型学习多组不同的注意力权重。每个注意力头都产生一个输出,最后通过线性变换和拼接得到最终的多头注意力输出。
在MHA计算整个过程中需要4个输入和输出维度都是d_model的Linear Layer,而整个Model的输入是(batch_size, seq_length, d_model),输出也是(batch_size, seq_length, d_model)。
MHA的优势:MHA能同时捕获输入数据的多个不同特性。不同的"头"可以分别专注于词序列的不同方面,例如语义、语法等。 Multi-headattentionallowsthemodeltojointlyattendtoinformationfromdifferent representationsubspacesatdifferentpositions. MHA将隐状态向量分成多个头,形成多个子语义空间,可以让模型去关注不同维度语义空间的信息。
【2】MHA的计算公式 Multi-Head-Attention的公式: Multi-Head-Attention就是在单头Self-Attention的基础上,在隐状态维度的方向将其切分成H个头,再分别计算得到headi 结果后进行concat【隐状态维度=hidden_size=d_model】
公式如下所示:
假设H=3,d_model=dv=dq=dk=6,seq_length=3图解过程如下所示:
【推荐阅读】https://zhuanlan.zhihu.com/p/626820422
在《Attention Is All You Need》论文原文中解释了多头的作用:将隐状态向量分成多个头,形成多个子语义空间,可以让模型去关注不同维度语义空间的信息(或者说让模型去关注不同方面的信息)。
常见的设置是dk=dv= d/h,
对于LLAMA2-7b 有 d=4096,h=32,dk=dv=128
对于LLAMA2-70b有 d=8192,h=64,dk=dv=128
【3】MHA的计算流程图解 MHA多头注意力计算过程图解1 该图MHA的计算流程如下:
1.输入句子,这里是Thinking Machines; [Input sentence]
2.对句子进行tokenize和embedding并得到X, 后续计算需使用encoder输出R代替X。 [Embed each word]。 X维度为(seq_length,d_model)=(2,4);
3.将WQ,WK,WV划分多头h=8,,构造多头注意力权重矩阵 。[Split into 8 heads]
矩阵维度为(d,d_model)=(3,4);
4.将X分别和 8 组权重矩阵 做内积, 得到8组 。每组 通过注意力公式分别计算结果,得到 8 组 ;[Calculate attention]
维度(seq_length,d)=(2,3); 维度为(seq_length,d)=(2,3)
5.将8组注意力结果 Concatenate 起来, 得到临时Z',;再乘以投影矩阵 ,后,得到最终注意力结果 Z; [Concatenate Zi matrices then miltiply = Z]
Z'维度为(seq_length,d*h)=(2,24)。 维度为(d*h,d_model)=(24,4)。Z维度为(seq_length,d_model)=(2,4);
【此处seq_length=2,d_model=4,dq=dv=dk=3,h=8】
【推荐阅读】图解Transformer最关键模块MHA【20240214】
https://mp.weixin.qq.com/s/-jeuRsrOehG5ydba0txLug
MHA多头注意力计算过程图解2
https://mp.weixin.qq.com/s/QjK_gL4pHCLHNi6y4vsM6g
MHA多头注意力计算过程图解3
https://mp.weixin.qq.com/s/yhEa1olOjyQ4oaDhHocB6A
【4】MHA的相关细节解析 a.《Attention Is All You Need》中:Multi-head attention allows the model to jointly attendtoinformationfromdifferentrepresentationsubspacesatdifferentpositions. b.MHA在d_model方向将qkv分成多个头,形成多个维度较低的子语义空间。多个head各自学习到的小份attention侧重点可能略有不同,最终再将各个小份attention的信息综合起来,增强整个Attention模型的表达能力。
2.引用对Multi-Head-Attention的结论: a.对于大部分query,每个头都学习了某种固定的pattern模式,而且12个头中大部分pattern是差不多的,但是总有少数的pattern才能捕捉到语法/句法/词法信息。 b.越靠近底层的attention,其pattern种类越丰富,关注到的点越多,越到顶层的attention,大部分head的pattern趋同,最后留下来的极少不相同的head的就是这个模型表达语义信息的head。 c.head数越少,pattern会更倾向于token关注自己本身(或者其他的比较单一的模式,比如都关注CLS)。 d.多头的核心思想应该就是ensemble,如随机森林一样,将特征切分,每个head就像是一个弱分类器,让最后得到的embedding关注多方面信息,不要过拟合到某一种pattern上。 e.已有论文证明head数目不是越多越好,bert-base上实验的结果为8、16最好,太多太少都会变差。 f.multi-head-attention中大部分头没有捕捉到语法/句法信息,但是笔者这里没办法做出断言说它们是没有用的,具体还是要看下游任务对其的适配程度。 【引用链接】https://zhuanlan.zhihu.com/p/626820422 3.MHA的多头使用几个头比较好?
已有论文证明,头数不是越多越好,论文实验结果如下:
由A组实验,可以看到多头h=8/16时,PPL/BLEU最好,h=4/32次之,h=1最差。
头的数量不是越多越好,头越多,每个qkv分到的维度就会降低,各个子空间的维度越低,表达能力也就变差,也未必能更好的捕捉到语法/句法/词法信息。具体多头的数量要视模型规模,任务而定。
目前可以看到的趋势是,模型越大(hidden size越大),头数的增多越能带来平均效果上的收益(或者说允许注意力头增大而不影响子空间的学习能力)。目前LLM主流的头数视乎模型结构和规模大致有12、16、24、48、96这样一些主流设置。
4.Multi-head Attention 存在什么问题? 多头注意力中的内存带宽挑战:问题的关键在于内存开销。像Transformer这样的自回归模型中的每个解码步骤都需要加载解码器权重以及所有注意力键和值。这个过程不仅计算密集,而且对内存带宽要求很高。随着模型规模的增长,这种开销也会增加,使得扩展变得越来越艰巨。
5.为什么Transformer需要 使用多头注意力机制?-【同1】 总结一下就是,Bert类的attention矩阵是稀疏的(这个是双向信息导致的),因此attention矩阵可以被低秩分解,也就是说虽然矩阵很大但是信息却很少。D远大于d的情况下,T*D 的向量得到的attention矩阵和 T*d 的向量得到的attention矩阵信息量差不多,那不如把D切成若干个d,搞出若干个头来,相当于融合了,因此有了多头。 苏神虽然和本文得出的结论是一致的,都是多头融合理论 ,不过苏神给出了一个全新的视角,非常有意思。
【引用链接】https://zhuanlan.zhihu.com/p/626820422
原问题: BERT中,multi-head 768*64*12与直接使用768*768矩阵统一计算,有什么区别? 先说结论: 区别在于模型容量增加,带来模型表现力的提升。
https://www.zhihu.com/question/446385446/answer/1982483918
【5】MHA的代码实现 #TheAnnotatedTransformer中的MHA代码实现 defattention(query,key,value,mask=None,dropout=None): "Compute'ScaledDotProductAttention'" d_k=query.size(-1) scores=torch.matmul(query,key.transpose(-2,-1))\ /math.sqrt(d_k) ifmaskisnotNone: scores=scores.masked_fill(mask==0,-1e9) p_attn=F.softmax(scores,dim=-1) ifdropoutisnotNone: p_attn=dropout(p_attn) returntorch.matmul(p_attn,value),p_attn classMultiHeadedAttention(nn.Module): def__init__(self,h,d_model,dropout=0.1): ''' h:headnumber ''' super(MultiHeadedAttention,self).__init__() assertd_model%h==0 #Weassumed_valwaysequalsd self.d=d_model//h self.h=h self.linears=clones(nn.Linear(d_model,d_model),4) self.attn=None self.dropout=nn.Dropout(p=dropout) defforward(self,query,key,value,mask=None): ifmaskisnotNone: #Samemaskappliedtoallhheads. mask=mask.unsqueeze(1) nbatches=query.size(0) #1)Doallthelinearprojectionsinbatchfromd_model=>hxd query,key,value=\ [l(x).view(nbatches,-1,self.h,self.d).transpose(1,2) forl,xinzip(self.linears,(query,key,value))] #2)Applyattentiononalltheprojectedvectorsinbatch. x,self.attn=attention(query,key,value,mask=mask, dropout=self.dropout) #3)"Concat"usingaviewandapplyafinallinear. x=x.transpose(1,2).contiguous()\ .view(nbatches,-1,self.h*self.d) returnself.linears[-1](x))#1)Doallthelinearprojectionsinbatchfromd_model=>hxd_k query,key,value=\ [l(x).view(nbatches,-1,self.h,self.d_k).transpose(1,2) forl,xinzip(self.linears,(query,key,value))] #重写这段代码 #第一行把QKV分别经过一层Linear变换,tensor size不变,第二行将QKV的d_model维向量分解为h * d_k。 query,key,value=[l(x)forl,xinzip(self.linears,(query,key,value))] query,key,value=[x.view(nbatches,-1,self.h,self.d_k).transpose(1,2) forxin(query,key,value)] #跑一个self-attention的实例,作为输入,query/key/value的shape为(batch_size,seq_lengh,d_model): h=8 d_model=512 batch_size=1 seq_length=10 model=MultiHeadAttention(h,d_model) query=torch.randn([batch_size,seq_length,d_model]) key=query value=query print('Inputsize:'+str(query.size()))https://github.com/zzzichen277/LLM_CodingSet/blob/main/10.multiheadattention%E5%8F%98%E6%8D%A2%E8%BF%87%E7%A8%8B%E4%BB%A3%E7%A0%81.ipynb
【推荐阅读】https://mp.weixin.qq.com/s/lMXf6zRWrH4SV8YbQ2Jzcw
【6】Self-Attention代码LLama实现 使用hf的代码实现(删掉RoPE)Multi-headed attention
#https://zhuanlan.zhihu.com/p/643829565 fromtorchimportnn classLlamaAttention(nn.Module): """Multi-headedattentionfrom'AttentionIsAllYouNeed'paper""" def__init__(self,config lamaConfig):super().__init__() self.config=config self.hidden_size=config.hidden_size#隐藏层大小 self.num_heads=config.num_attention_heads#注意力头的数量 self.head_dim=self.hidden_size//self.num_heads#每个注意力头的维度 self.max_position_embeddings=config.max_position_embeddings#最大位置编码长度 #线性变换层,用于产生查询(q)、键(k)、值(v)向量 self.q_proj=nn.Linear(self.hidden_size,self.num_heads*self.head_dim,bias=False) self.k_proj=nn.Linear(self.hidden_size,self.num_heads*self.head_dim,bias=False) self.v_proj=nn.Linear(self.hidden_size,self.num_heads*self.head_dim,bias=False) self.o_proj=nn.Linear(self.num_heads*self.head_dim,self.hidden_size,bias=False) defforward( self, hidden_states:torch.Tensor,#输入的隐藏状态张量 attention_mask:Optional[torch.Tensor]=None,#注意力掩码,可选 position_ids:Optional[torch.LongTensor]=None,#位置编码,可选 past_key_value:Optional[Tuple[torch.Tensor]]=None,#过去的键值对,可选 output_attentions:bool=False,#是否输出注意力权重 use_cache:bool=False,#是否使用缓存 )->Tuple[torch.Tensor,Optional[torch.Tensor],Optional[Tuple[torch.Tensor]]]: bsz,q_len,_=hidden_states.size() #获得qkv向量 query_states=self.q_proj(hidden_states).view(bsz,q_len,self.num_heads,self.head_dim).transpose(1,2) key_states=self.k_proj(hidden_states).view(bsz,q_len,self.num_heads,self.head_dim).transpose(1,2) value_states=self.v_proj(hidden_states).view(bsz,q_len,self.num_heads,self.head_dim).transpose(1,2) #拼接kvcache kv_seq_len=key_states.shape[-2] ifpast_key_valueisnotNone: kv_seq_len+=past_key_value[0].shape[-2] ifpast_key_valueisnotNone: #reusek,v,self_attention key_states=torch.cat([past_key_value[0],key_states],dim=2) value_states=torch.cat([past_key_value[1],value_states],dim=2) past_key_value=(key_states,value_states)ifuse_cacheelseNone #计算attention权重 attn_weights=torch.matmul(query_states,key_states.transpose(2,3))/math.sqrt(self.head_dim) #加入mask矩阵,decoder-only为下三角 ifattention_maskisnotNone: attn_weights=attn_weights+attention_mask dtype_min=torch.tensor( torch.finfo(attn_weights.dtype).min,device=attn_weights.device,dtype=attn_weights.dtype ) attn_weights=torch.max(attn_weights,dtype_min) #计算softmax,这里需要从fp16升为fp32 #upcastattentiontofp32 attn_weights=nn.functional.softmax(attn_weights,dim=-1,dtype=torch.float32).to(query_states.dtype) attn_output=torch.matmul(attn_weights,value_states) attn_output=attn_output.transpose(1,2) attn_output=attn_output.reshape(bsz,q_len,self.hidden_size) attn_output=self.o_proj(attn_output) ifnotoutput_attentions: attn_weights=None returnattn_output,attn_weights,past_key_valueLLaMA的huggingface实现:https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/modeling_llama.py#L232
meta仓库的原版实现:https://github.com/facebookresearch/llama/blob/main/llama/model.py#L76 【7】MHA.MQA.GQA对比 GQA与MHA、MQA的对比如下图所示:
当前有如下 3 种主流的 Attention 计算方式:
a.MHA拥有H份查询头Q和H份KV对。查询头Q使用各自对应K,V对进行计算。所有头的K和V权重不共享。MHA需要的缓存量很大。(Multi-head Attention)
b.MQA拥有H份查询头和1份KV对。所有的查询头Q共享同1份K.V对进行计算。MQA从减少K和V矩阵的参数量,加快解码器推断的速度,但生成质量随着降低。(Multi-Query Attention)
c.GQA拥有H份查询头和G份KV对。将查询头Q分成G组且组内共享同份KV对进行计算。GQA是MHA效果和MQA速度的折中方案。(Grouped-Query Attention)
GQA-G是指具有G组的Grouped-Query Attention。当G=1时,就是MQA。当G=H时,就是MHA。