首页 > 编程语言 >LLM大模型: llama源码要点解读(一)

LLM大模型: llama源码要点解读(一)

时间:2024-06-11 09:43:29浏览次数:19  
标签:LLM self 源码 proj llama 维度 hidden config

  transformer火了之后,基于transformer架构的llama也火了,可能的原因:

  • 来自meta,一线互联网大厂,质量有保证;自称70b参数的表现比chatGPT3还好(Llama 2:Open Foundation and Fine-Tuned Chat Models)!
  • 可能会成为大模型界的Android:各种基于llama的微调和应用会越来越多(llama的模型的参数量7B、13B、70B,凡是和这个参数量一样的的大模型,很有可能是基于llama二次改造的
  所以学习llama的源码是非常重要的(这不废话么?);整个transformer的源码在这: https://github.com/huggingface/transformers transformer里面收录所有的模型都在这: https://github.com/huggingface/transformers/tree/main/src/transformers/models 那些听说过的、没听说过的大模型在这里都能找到,当然也包括llama:https://github.com/huggingface/transformers/tree/main/src/transformers/models/llama   llama源码不多,就这些,如下:从名字就能看出来这些文件的核心功能是啥!

       

  核心类不多,就这些:

     

     打开modeling_llama文件:

      1、第一个映入眼帘的就是归一化了:

class LlamaRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
        """
        LlamaRMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size)) #初始化权重为1
        self.variance_epsilon = eps #防止分母为0

    def forward(self, hidden_states):
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)

  用公式总结就是:

     

      那么问题来了:llama为啥要用RMSNorm,而不用batchNorm或layerNorm?

       3种norm方式综合对比:

  • 计算开销:RMSNorm > LayerNorm > BatchNorm。RMSNorm的计算开销最低,因为它不需要计算均值。
  • 对小批量数据的适用性:RMSNorm和LayerNorm均优于BatchNorm,适用于小批量甚至单个样本的数据。
  • 适用场景:RMSNorm和LayerNorm在序列建模、NLP以及需要处理变长输入的任务中表现更好,而BatchNorm更适合于图像处理和需要大批量数据训练的任务。
  • 训练稳定性:RMSNorm通过均方根归一化,在深层神经网络中提供了较为稳定的训练效果。

      2、(1)第二个重要的类就是MLP,有的地方也要FFN,就是常见的深度神经网络层:

class LlamaMLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.hidden_size = config.hidden_size
        self.intermediate_size = config.intermediate_size # 中间层大小
        self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias) #输入升维到中间层
        self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias) #输入升维到中间层
        self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=config.mlp_bias) #中间层到输出层
        self.act_fn = ACT2FN[config.hidden_act] #激活函数

    def forward(self, x):
        if self.config.pretraining_tp > 1:
            slice = self.intermediate_size // self.config.pretraining_tp  #切片
            gate_proj_slices = self.gate_proj.weight.split(slice, dim=0) #输入切片
            up_proj_slices = self.up_proj.weight.split(slice, dim=0)
            down_proj_slices = self.down_proj.weight.split(slice, dim=1) #输出切片

            gate_proj = torch.cat(
                [F.linear(x, gate_proj_slices[i]) for i in range(self.config.pretraining_tp)], dim=-1  #每个切片并行执行线性变换后拼接
            )
            up_proj = torch.cat([F.linear(x, up_proj_slices[i]) for i in range(self.config.pretraining_tp)], dim=-1) #每个切片并行做线性变换后拼接

            intermediate_states = (self.act_fn(gate_proj) * up_proj).split(slice, dim=2)
            down_proj = [
                F.linear(intermediate_states[i], down_proj_slices[i]) for i in range(self.config.pretraining_tp)
            ]
            down_proj = sum(down_proj)#结果相加
        else:
            down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))

        return down_proj

    和传统的FFN比,llama的MLP只有1点是一样的:输入通过线性变换升维到intermediate中间层,再从intermediate中间层降维到输出层!其他的差异较大,主要体现在:

  • 输入切片,并行处理
  • gate经过激活函数后继续和up相乘;整个过程图示如下:

      

     那么问题来了:为啥要把input分成gate和up,gate经过激活函数后再乘以up了?

   (2)记得10多年前大数据这个概念刚火热时,很多互联网公司利用用户的基础画像、行为数据等做搜广推。这些数据类从业人员最常用炫技的说辞之一:通过亿级的数据维度快速、精准地做推荐!当时我就纳闷了:用户画像一般有几百到几千维度,用户行为数据大概也这个量级,某些从业人员所谓的亿级别维度是哪来的?后来找了好多资料才发现,这么高维度数据的来源竟然是:特征组合!比如“女性”+“年龄” 两两组合成新维度,计算化妆品、服装等的ctr; “男性” + “运动” 两个原本独立的维度两两组合,计算体育用品、体育类视频的ctr;这只是基础特征维度的两两组合,还有三个、四个、五个、甚至更多特征维度的组合了?这么来看,基础特征组合成上亿个维度完全是有可能的!原本线性不可分的样本,经过维度组合后,产生了非线性特征,更容易区分样本,秒啊!顺着这个思路,从代数角度解释神经网络的一些特性就容易多了:

  • 为什么神经网络要用激活函数?这里的二阶到N阶,展开后不就是特征的2阶到N阶的组合么

      

  •  神经网络为什么要先维,再降维? 升维的核心是特征组合:维度提高了激活函数就多啦,组合的特征就多了,能捕捉到的非线性特征就多了!为什么又要降维了?去掉无用的特征组合

     回到llama这里来:输入为什么要分成gate和up?为什么gate经过激活函数后还要和up相乘了?

  • 激活函数:参考上述,激活函数经过tylor展开后,不就是特征的N阶组合么?
  • 和up相乘,不也是特征之间的两两组合么?

  说到底,干的这些事,最终都是做特征的各种花式组合,让线性不可分的数据产生非线性特征,为最终的分类或回归任务产生强特征

 

其他要点总结:

  1、做所有的重要操作前(包括但不限于attention、softmax等)前都要乘以一个矩阵做线性变换,所以这些操作都是在新的矩阵空间进行的,目的是和之前的旧空间隔开来,避免互相影响:每个矩阵空间只干一件事,如果要做其他事,继续通过矩阵乘法进入下一个空间操作,使得每个矩阵空间都是专用的,互不干扰(有点像每个厕所坑位只能蹲一个人,坑位之间严格隔开,避免互相影响拉便便)!

  2、特征组合让线性不可分变成线性可分举例:经典的XOR问题;整个思路很简单:现有的维度分不开了怎么办?那就生成新的能分开的维度呗!怎么生成新维度了?下面这个例子用的是旧维度两两相乘。实际生产环境,还可以N个维度互相相乘,或则N个旧维度之间线性组合(神经网络不就是这么干的嘛!)

参考:

1、https://blog.csdn.net/qq_35812205/article/details/136587013  LLM2模型

2、https://www.bilibili.com/video/BV1qj411y7kF/?spm_id_from=333.788&vd_source=241a5bcb1c13e6828e519dd1f78f35b2    transformers源码阅读——如何看懂模型代码(以llama为例)

标签:LLM,self,源码,proj,llama,维度,hidden,config
From: https://www.cnblogs.com/theseventhson/p/18240871

相关文章