LLaMA简介
LLaMA(Large Language Model Meta AI)是由Meta开发的一种大规模语言模型,旨在提高自然语言处理任务的性能。LLaMA基于Transformer机构,并经过大规模数据训练,以便在多种语言任务中表现出色。
LLaMA所采用的Transformer结构和细节,与标准的Transformer结构不同的地方是包括了采用前置层归一化(Pre-normalization)并使用RMSNorm归一化函数(Normalizing Function)、激活函数更换为了SwiGLU,并使用了旋转位置嵌入(RoPE),整体Transformer架构与GPT-2类似。
RMSNorm归一化函数
为了使得模型训练的过程更加稳定,GPT-2相较于GPT就引入了前置层归一化方法,将第一个归一化移动到了多头注意力层之前,第二个归一化也移动到了全连接层之前,同时残差链接的位置也调整到了多头注意力层与全连接层之后。归一化中也采用了RMSNorm归一化函数。针对输入向量RNSNorm函数的计算公式如下:
代码实现如下:
class LLaMARMSNorm(nn.Module):
def __init__(self,hidden_size,eps=1e-6):
super().__init__()
self.weight=nn.Parameter(torch.ones(hidden_size))
self.variance_epsilon=eps
def forward(self,hidden_states):
input_dtype=hidden_states.dtype
variance=hidden_states.to(torch.float32).pow(2).mean(-1,keepdim=True)
hidden_states=hidden_states*torch.rsqrt(variance+self.variance_epsilon)
return (self.weight*hidden_states).to(input_dtype)
SwiGLU激活函数
SwiGLU激活函数是相较于ReLU函数在大部分测评中都有不少的提升。在LLaMA中全连接层使用带有SwiGLU激活函数的FFN(Position-wise Feef-Forward Network)计算公式如下:
其中Swish函数是Sigmoid函数。当取不同的β时,激活函数的图像如下图所示。当β趋近于0时,Swish函数趋近于线性函数y=x;当β趋近于无穷大时,Swish函数趋近于ReLU函数。
LLaMA中直接将FFN中的ReLU替换为SwiGLU,并将维度放缩为(2/3)*4d
旋转位置嵌入(RoPE)
在位置编码上,使用旋转位置嵌(Rotary Positional Embeddings, RoPE)入代替原来的绝对位置编码。RoPE 借助了 复数的思想,出发点是通过绝对位置编码的方式实现相对位置编码。其目标是通过q,k添加绝对位置信息:
import torch
def precompute_freqs_cis(dim: int, end: int, constant: float = 10000.0):
'''
计算cos和sin的值,cos值在实部,sin值在虚部,类似于 cosx+j*sinx
:param dim: q,k,v的最后一维,一般为emb_dim/head_num
:param end: 句长length
:param constant: 这里指10000
:return:
复数计算 torch.polar(a, t)输出, a*(cos(t)+j*sin(t))
'''
# freqs: 计算 1/(10000^(2i/d) ),将结果作为参数theta
# 形式化为 [theta_0, theta_1, ..., theta_(d/2-1)]
freqs = 1.0 / (constant ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) # [d/2]
# 计算m
t = torch.arange(end, device=freqs.device) # [length]
# 计算m*theta
freqs = torch.outer(t, freqs).float() # [length, d/2]
# freqs形式化为 [m*theta_0, m*theta_1, ..., m*theta_(d/2-1)],其中 m=0,1,...,length-1
# 计算cos(m*theta)+j*sin(m*theta)
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # complex64
# freqs_cis: [cos(m*theta_0)+j*sin(m*theta_0), cos(m*theta_1)+j*sin(m*theta_1),), ..., cos(m*theta_(d/2-1))+j*sin(m*theta_(d/2-1))]
# 其中j为虚数单位, m=0,1,...,length-1
return freqs_cis # [length, d/2]
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)] # (1, length, 1, d/2)
return freqs_cis.view(*shape) # [1, length, 1, d/2]
def apply_rotary_emb(xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor,):
# 先将xq维度变为[bs, length, head, d/2, 2], 利用torch.view_as_complex转变为复数
# xq:[q0, q1, .., q(d-1)] 转变为 xq_: [q0+j*q1, q2+j*q3, ..., q(d-2)+j*q(d-1)]
xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2)) # [bs, length, head, d/2]
# 同样的,xk_:[k0+j*k1, k2+j*k3, ..., k(d-2)+j*k(d-1)]
xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))
freqs_cis = reshape_for_broadcast(freqs_cis, xq_) # [1, length, 1, d/2]
# 下式xq_ * freqs_cis形式化输出,以第一个为例, 如下
# (q0+j*q1)(cos(m*theta_0)+j*sin(m*theta_0)) = q0*cos(m*theta_0)-q1*sin(m*theta_0) + j*(q1*cos(m*theta_0)+q0*sin(m*theta_0))
# 上式的实部为q0*cos(m*theta_0)-q1*sin(m*theta_0),虚部为q1*cos(m*theta_0)+q0*sin(m*theta_0)
# 然后通过torch.view_as_real函数,取出实部和虚部,维度由[bs, length, head, d/2]变为[bs, length, head, d/2, 2],最后一维放实部与虚部
# 最后经flatten函数将维度拉平,即[bs, length, head, d]
# 此时xq_out形式化为 [实部0,虚部0,实部1,虚部1,..., 实部(d/2-1), 虚部(d/2-1)]
xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3) # [bs, length, head, d]
# 即为新生成的q
xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)
return xq_out.type_as(xq), xk_out.type_as(xk)
if __name__=='__main__':
# (bs, length, head, d)
q = torch.randn((2, 10, 12, 32)) # q=[q0, q1, .., qd-1]
k = torch.randn((2, 10, 12, 32))
v = torch.randn((2, 10, 12, 32))
freqs_cis= precompute_freqs_cis(dim=32, end=10, constant= 10000.0)
# print(freqs_cis.detach().numpy())
q_new, k_new = apply_rotary_emb(xq=q, xk=k, freqs_cis=freqs_cis)
print()
Alpace
Alpaca是在LLaMA基础上使用52K指令数据精调的预训练模型;
微调
第一步:构造175条self-instruct种子示例任务;
第二步:基于上述种子任务,利用text-davinci-003爬取指令数据;
第三步:使用爬取下来的52k指令数据在LLaMA 进行精调,最终得到Alpaca;
Self-instruct数据构造
{
"id":"000000",
"name":"nnnnnn",
"instruction": "Find the four smallest perfect numbers.",
"instances": [{ "input": "", "output": "6, 28, 496, and 8128”}],
"is_classification": false
}
指令数据格式
- instruction:描述模型需要执行的指令内容;
- input:任务上下文或输入信息,例如当指令是“对文章进行总结”,则input是文章内容;
- output:由text-davinci-003生成的针对指令的回复;
LLaMA-2
2023年7月,meta推出了LLaMA-2开源大模型,并推出了LLaMA-2-chat对话模型;
与上一代LLaMA主要区别体现在更多的训练数据、更长的上下文窗口、GQA技术等;
结构上的变动主要体现在GQA和FFN缩放上:
- MHA改成GQA:整体参数量会有减少;
- FFN模块矩阵维度由扩充:增强泛化能力,整体参数量增加;
- 上下文长度时是LLaMA两倍:训练语料增加约40%,体现在1.4T->2.0T的okens llama2-34B和llama2-70B使用了GQA,加速模型训练和推理速度;
GQA
GQA和MQA都是注意力的变体,其中多个查询头关注相同的键和值头,以减少推理过程中 KV 缓存的大小,并可以显著提高推理吞吐量。
MHA、GQA、MQA的区别和联系,具体的优点如下:
- Mutil-Head Attention 因为自回归模型生成回答时,需要前面生成的KV缓存起来,来加速计算。
- Multi-Query Attention 多个头之间可以共享KV对,因此速度上非常有优势,实验验证大约减少30-40%吞吐。
- Group Query Attention 没有像MQA那么极端,将query分组,组内共享KV,效果接近MQA,速度上与MQA可比较。
Llama-2中使用了8个KV映射,即GQA-8,GQA在多数任务上与MHA效果相当,且平均效果优于MQA;GQA和MQA均比MHA有更好的吞吐量;
总结
LLaMA
- 开源大模型繁荣发展的开端,一系列相关工作均基于LLaMA开展;
- 模型规模7B、13B、33B、65B满足了开发者和研究者的不同需求;
Alpaca:通过少量的指令精调赋予LLaMA指令理解与执行的能力
Llama-2
- LLaMA的二代模型,相关模型性能进一步提升,模型可商用
- 推出官方对⻬的Chat版本模型,采用了完整的RLHF链条;
Code Llama:专注于代码能力的LLaMA模型,最好的模型代码能力接近GPT-4效果,模型可商用
标签:系列,LLama,模型,torch,freqs,GQA,LLaMA,theta From: https://blog.csdn.net/cancer_s/article/details/143401453