- 来自meta,一线互联网大厂,质量有保证;自称70b参数的表现比chatGPT3还好(Llama 2:Open Foundation and Fine-Tuned Chat Models)!
- 可能会成为大模型界的Android:各种基于llama的微调和应用会越来越多(llama的模型的参数量7B、13B、70B,凡是和这个参数量一样的的大模型,很有可能是基于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)) #初始化权重为1self.variance_epsilon = eps #防止分母为0def forward(self, hidden_states):input_dtype = hidden_states.dtypehidden_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 = configself.hidden_size = config.hidden_sizeself.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为例)