赞
踩
本文先全面介绍Mistral 7B,然后再全面介绍Mixtral 8x7B
对于后者,毕竟OpenAI 团队一直对 GPT-4 的参数量和训练细节守口如瓶。早些时候,有人爆料 GPT-4 是采用了由 8 个专家模型组成的集成系统。后来又有传闻称,ChatGPT 也只是百亿参数级的模型(大概在 200 亿左右)
传闻无从证明,但 Mixtral 8x7B 可能提供了一种「非常接近 GPT-4」的开源选项,特此,本文全面解析下:从原理解析到代码解读(在此文之前,尚没有资料扒得像本文这样如此之细)
23年5月,DeepMind和Meta的三位前员工在巴黎共同创立了Mistral AI(其CEO Arthur Mensch此前在DeepMind巴黎工作,CTO Timothée Lacroix和首席科学家Guillaume Lample则在Meta共同参与过LLaMA一代的研发,很像当年OpenAI的部分员工出走成立Anthropic啊)
23年9.27,他们发布了第一个基座大模型,即Mistral 7B (这是当时Mistral AI关于Mistral 7B发布的新闻 )
Mistral 7B对应的论文为《Mistral 7B》称( 另,这是其GitHub地址),以下是「模型参数图」
所以你看上面的「模型参数图」,维度(dim):4096,总计32个头(n_heads),每个头的维度(head_dim):128,这一眼可以看出来,而n_kv_heads是啥呢?
咋一看好像不太好理解 是不?其实,正是因为Mistral用了GQA,n_heads指的是Q的头数,n_kv_heads指的是K、V的头数
不过要注意的是,与上图中间所示部分不太一样的地方在于:
上图中间所示部分中,Q的头数是K V头数的2倍
但在Mistral的GQA中,Q的头数是K V头数的4倍
- import torch
- from typing import Tuple
-
-
- def precompute_freqs_cis(dim: int, end: int, theta: float) -> torch.Tensor:
- freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
- t = torch.arange(end, device=freqs.device) # type: ignore
- freqs = torch.outer(t, freqs).float() # type: ignore
- return torch.polar(torch.ones_like(freqs), freqs) # complex64
-
-
- def apply_rotary_emb(
- xq: torch.Tensor,
- xk: torch.Tensor,
- freqs_cis: torch.Tensor,
- ) -> Tuple[torch.Tensor, torch.Tensor]:
- xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
- xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))
- freqs_cis = freqs_cis[:, None, :]
- xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(2)
- xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(2)
- return xq_out.type_as(xq), xk_out.type_as(xk)
与Mistral 7B同期发布的Mistral 7B – Instruct(We also provide a model fine-tuned to follow instructions,Mistral 7B –Instruct),在MT-Bench的表现可以略微超过LLaMA2 13B –Chat模型
再后来到23年12月时,instruct升级到了0.2版,但此时的上下文长度依然只有8K,但好在24年3月随着Mistral 7B 0.2版的发布,Mistral 7B-Instruct-0.2也顺势做了升级,上下文长度扩展到了32K
更具体的细节,以及关于Mistral instruct 0.2的微调,则详见此文《七月论文审稿GPT第3.1版和第3.2版:通过paper-review数据集分别微调Mistral、gemma》
vanilla attention的操作次数在序列长度上是二次型的,记忆量随着token数量线性增加。在推理时,由于缓存可用性的降低,这导致了更高的延迟和更小的吞吐量(The number of operations in vanilla attention is quadratic in the sequence length, and the memory increases linearly with the number of tokens. At inference time, this incurs higherlatency and smaller throughput due to reduced cache availability)
为了缓解这个问题,Mistral 7B使用滑动窗口注意力(sliding window attention)
固定的注意力长度意味着可以使用滚动缓存来限制的缓存大小(A fixed attention span means that we can limit our cache size using a rollingbuffer cache)
如果把缓冲区比作一座仓库,每存进一个新东西,都会占据相应的位置,而仓库的总容量是固定的,当仓库被装满时,就会把最早放入的东西移除,让新的物品继续进仓,相当于入仓时间更接近当前时间的物品则会留在仓库中,如此,即能在节约资源的同时保留一定长度的序列
在生成序列时,需要一个一个地预测token,因为每个token都以前面的token为条件。然而,prompt是提前知道的,可以用prompt预填充(k, v)缓存,即
23年12月8日,Mistral AI 在 X 平台甩出一条磁力链接(当然,后来很多人打开一看,发现是接近 87 GB 的种子)
看上去,Mixtral 8x7B的架构此前传闻的GPT-4架构非常相似(很像传闻中GPT-4的同款方案),但是「缩小版」:
在发布后 24 小时内,已经有开发者做出了在线体验网站:https://replicate.com/nateraw/mixtral-8x7b-32kseqlen
两天后的23年12.11日,Mistral AI团队对外正式发布 Mixtral 8x7B,其在大多数基准测试中都优于 Llama 2 70B,推理速度提高了 6 倍,且它在大多数标准基准测试中匹配或优于 GPT3.5
为免歧义,补充说明下,Mistral AI团队目前总共发布了两个模型
特意注意,一个mis 一个mix,本质不同
而Mixtral 8x7B是一个纯解码器模型,下图是Mixtral的核心参数(可以把它和Mistral的核心参数做个对比)
如下图所示,传入模型的各个token在经过Attention层及残差连接后,进一步将由路由(Gating/Router)导向2个expert(FFN)中,之后对expert的输出进行加权聚合,再经过残差连接得到当前层的输出
即对于给定的输入,MoE模块的输出由“专家网络输出的加权和”决定,其中权重由“门控网络的输出”确定(The output of the MoE module for a given input x is determined by the weighted sum of the outputs of the expert networks, where the weights are given by the gating network’s output.)
当给定个专家网络,则专家层(expert layer)的输出为:
如果门控向量稀疏,我们可以避免计算门为零的专家输出(If the gating vector is sparse, we can avoid computing the outputs of experts whose gates are zero)。有多种实现G(x)的可选方法,但一种简单且高性能的方法是通过对线性层的Top-K logits进行softmax(but a simple and performant one is implemented by taking the softmax over the Top-K logits of a linear layer [28])
如七月官网的「LLM与多模态论文100课程」中一学员所说,关于以上内容的更多细节,可以进一步阅读此论文:Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer
MoE层能够在具备高性能专用内核的单个GPU上高效运行
- 例如,Megablocks将MoE层的前馈网络(FFN)操作转换为大型稀疏矩阵乘法(Megablocks [13] casts the feed-forward network (FFN) operations of the MoE layer as large sparse matrix multiplications),从而显著提升了执行速度
并且可以自动处理不同专家被分配可变数量token的情况(naturally handling cases where different experts get a variable number of tokens assigned to them.)- 此外,通过标准模型并行技术和一种名为专家并行(EP)的特殊分区策略,MoE层可以在多个GPU上进行分布
Moreover, the MoE layer can be distributed to multiple GPUs through standard Model Parallelism techniques, and through a particular kind of partitioning strategy called Expert Parallelism (EP) [28].
在MoE层执行过程中,旨在由特定专家处理的token会被路由到相应的GPU进行处理,并将专家输出返回到原始token位置During the MoE layer’s execution, tokens meant to be processed by a specific expert are routed to the corresponding GPU for processing, and the expert’s output is returned to the original token location.
需要注意的是,在负载平衡方面,EP带来了挑战,因为均匀地分配工作负载至关重要以避免单个GPU过载或遇到计算瓶颈
Note that EP introduces challenges in load balancing, as it is essential to distribute the workload evenly across the GPUs to prevent overloading individual GPUs or hitting computational bottlenecks.
在Transformer模型中,MoE层独立应用于每个token,并替换了Transformer块的前馈(FFN)子块(In a Transformer model, the MoE layer is applied independently per token and replaces the feed-forward (FFN) sub-block of the transformer block)
对于Mixtral
综上,输入token 经过处理后得到输出(This means each token is routed to two SwiGLU sub-blocks with different sets of weights)
这个公式类似于GShard架构,不同之处是mixtral用MoE层替换所有FFN子块,而GShard替换所有其他块,并且GShard对分配给每个token的第二个专家使用更详细的门策略
Mixtral沿用了Mistral 7B中所采取的GQA机制,与传统的MHA(Multi-Head Attention)相比,主要是对Attention机制中的K、V表征维度进行控制,从而降低K、V对应的参数量,除GQA外相应地还有MQA(Multi-Query Attention),MQA可以认为是GQA的特例。相关维度如下表所示:
Q | K | V | |
MHA | hidden_dim | hidden_dim | hidden_dim |
GQA | hidden_dim | hidden_dim/n | hidden_dim/n |
MQA | hidden_dim | 1 | 1 |
其中n为K和V相对MHA参数量降低的比例,具体地,在Mixtral中n为4
关于GQA的更多细节详见此文《一文通透各种注意力:从多头注意力MHA到分组查询注意力GQA、多查询注意力MQA》
路由(Gating/Router)本质是一个线性层,输入维度为隐层维度hidden_dim、输出维度为expert数num_experts。正向传播过程中将被用作预测给定token对应输入各个expert的分值
self.gate = nn.Linear(self.hidden_dim, self.num_experts, bias=False)
至于路由处理的对象可以是Sentence-Level、Token-Level或者Task-Level
因此同样也是对各个样本分别进行路由,但其所路由的目标expert是有明确导向的,例如某样本的数据还提供有“所属任务”信息,通过该信息可明确将该样本导向某个专职负责对应任务的expert中
Mixtral采取了Token-Level的处理单位
我们将 Mixtral 与 Llama 2 系列和 GPT3.5 基础模型进行比较。Mixtral 在大多数基准测试中均匹配或优于 Llama 2 70B 以及 GPT3.5
在下图中的测试,衡量了质量与推理预算的权衡。与 Llama 2 相比,Mistral 7B 和 Mixtral 8x7B 更高效
下表给出了上图的详细结果
为了识别可能的缺陷,通过微调/偏好建模来纠正,测量了其在BBQ/BOLD 上的性能
与 Llama 2 相比,Mixtral 对 BBQ 基准的偏差较小。总体而言,Mixtral 在 BOLD 上比 Llama 2 显示出更积极的情绪
与 Mixtral 8x7B 一起发布还有 Mixtral 8x7B Instruct,其在Mixtral 8x7B的基础上通过监督微调和直接偏好优化(DPO)进行优化,以让之严格的遵循指令
关于什么是DPO及其原理细节,请参见此文《RLHF的替代之DPO原理解析:从RLHF、Claude的RAILF到DPO、Zephyr》
在MT-Bench上,它达到了8.30的分数,使其成为最好的开源模型,性能可与GPT3.5相媲美
如阿荀所说(本部分的base版本由我司大模型项目团队第二项目组的阿荀提供,我在其基础上陆陆续续做了大量的补充、说明 ),上文中关于mixtral一个比较反直觉的点是:
单个Mixtral层可以大体划分为Attention模块和MOE模块,以下重点关注MOE模块的前向传播过程
为确保大家可以以最快的速度理解各行代码的含义,我在阿荀分析的基础上拆成了以下六个步骤,且对每个步骤都加了额外的解释说明
- # 由Attention模块输出的hidden_states作为本部分的输入
- batch_size, sequence_length, hidden_dim = hidden_states.shape
- # 转换成(bs*seq_len, hidden_dim),即token-level
- hidden_states = hidden_states.view(-1, hidden_dim)
- # router_logits: (batch * sequence_length, n_experts)
- # (bs * seq_len, n_experts)
- router_logits = self.gate(hidden_states)
- # 在token-level(dim=1)进行softmax,即每个token都各自进行n_experts分类的输出
- routing_weights = F.softmax(router_logits, dim=1, dtype=torch.float)
- # routing_weights: (bs * seq_len, topk),是选取的experts对应的原始权重
- # selected_experts: (bs * seq_len, topk),是选取的experts的编号/索引号
- routing_weights, selected_experts = torch.topk(routing_weights, self.top_k, dim=-1)
- # 对原始权重重新归一化,使得所取出的experts权重加和等于1
- # routing_weights的具体样例见下文的【代码块A】
- routing_weights /= routing_weights.sum(dim=-1, keepdim=True)
- # final_hidden_states: (bs * seq_len, hidden_dim)
- # 由全0张量初始化
- # final_hidden_states将用于存储各token对应expert的聚合结果
- final_hidden_states = torch.zeros(
- (batch_size * sequence_length, hidden_dim), dtype=hidden_states.dtype, device=hidden_states.device
- )
- # selected_experts.shape: (bs*seq_len, topk)
- # torch.nn.functional.one_hot(selected_experts, num_classes=self.num_experts).shape: (bs*seq_len, topk, num_experts)
expert_mask = torch.nn.functional.one_hot(selected_experts, num_classes=self.num_experts).permute(2, 1, 0)
具体而言,下面这个张量- # 根据次序逐个取出expert模型
- for expert_idx in range(self.num_experts):
- expert_layer = self.experts[expert_idx]
- idx, top_x = torch.where(expert_mask[expert_idx])
上面这几行代码得好好解释下- # 如果exert_mask[expert_idx]不存在元素为1的值则跳过
- if top_x.shape[0] == 0:
- continue
-
- # 全部token的隐向量hidden_states中取出当前expert对应token的隐向量
- # current_state.shape: (top_x_length, hidden_dim)
- current_state = hidden_states[None, top_x_list].reshape(-1, hidden_dim)
-
- # 将取出的token隐向量传入expert模型进行前向传播得到返回
- # current_hidden_states.shape: (top_x_length, hidden_dim)
- # expert_layer的正向过程详见下文的【代码块D】
- current_hidden_states = expert_layer(current_state, routing_weights[top_x_list, idx_list, None])
-
- # 将当前expert的输出以加和的形式写入预先定义好的final_hidden_states张量中
- final_hidden_states.index_add_(0, top_x, current_hidden_states.to(hidden_states.dtype))
final_hidden_states = final_hidden_states.reshape(batch_size, sequence_length, hidden_dim)
- # 【代码块A】routing_weights
- # 每行对应1个token,第0列为其对应排位第1的expert、第1列为其对应排位第2的expert,元素值为相应权重
- [[0.5310, 0.4690],
- [0.5087, 0.4913],
- [0.5775, 0.4225],
- [0.5014, 0.4986],
- [0.5030, 0.4970],
- [0.5479, 0.4521],
- [0.5794, 0.4206],
- [0.5545, 0.4455],
- [0.5310, 0.4690],
- [0.5294, 0.4706],
- [0.5375, 0.4625],
- [0.5417, 0.4583],
- [0.5014, 0.4986],
- [0.5239, 0.4761],
- [0.5817, 0.4183],
- [0.5126, 0.4874]]
因为有:expert_mask记录有各个expert分别作为各个排位存在的时候,对应需要处理哪些token
故而有:expert_mask[expert_idx]从expert_mask中取出第expert_idx个expert将处理哪些token
第0行为该expert作为排位第1存在的时候处理的token
第1行为该expert作为排位第2存在的时候处理的token
- # 【代码块B】expert_mask[expert_idx]
- # 下述两行例子的物理含义为:
- # 第一行是“该expert作为排位1的exert存在时,需要处理第9个token;
- # 第二行是“该expert作为排位2的expert存在时,需要处理第10、11个token”
- [[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]]
- # 【代码块C】idx, top_x = torch.where(expert_mask[expert_idx])
- # 以上述expert_mask[expert_idx]样例为例,对应的torch.where(expert_mask[expert_idx])结果如下
- idx: [0, 1, 1]
- top_x: [9, 10, 11]
idx对应行索引,top_x对应列索引,例如张量expert_mask[expert_idx]中,出现元素1的索引为(0, 9)、(1, 10)、(1, 11)
从物理含义来理解,top_x实际上就对应着“关乎当前expert的token索引”,第9、第10、第11个token被“路由”导向了当前所关注的expert,通过top_x可以取到“需要传入该expert的输入”,也即第9、第10、第11个token对应的隐向量
并且通过行索引、列索引的组合routing_weights
- # 【代码块D】expert内部的前向传播
- def forward(self, hidden_states, routing_weights):
- current_hidden_states = self.act_fn(self.w1(hidden_states)) * self.w3(hidden_states)
- current_hidden_states = self.w2(current_hidden_states)
- return routing_weights * current_hidden_states
其入参不仅有expert相应token的隐向量,还有对应expert的权重,整体是一个基于swiGLU激活的FFN
最后对FFN的输出进行加权得到该expert的实际输出,因此加权处理是在expert的内部就已经进行了
- # 查看与当前expert有关的final_hidden_states部分,即final_hidden_states[top_x]
- [[0., 0., 0., ..., 0., 0., 0.],
- [0., 0., 0., ..., 0., 0., 0.],
- [0., 0., 0., ..., 0., 0., 0.]]
final_hidden_states.index_add_(0, top_x, current_hidden_states.to(hidden_states.dtype))
- [[ 0.0938, 0.0509, -0.0689, ..., -0.0182, -0.0246, 0.0468],
- [ 0.1246, 0.0642, 0.0015, ..., 0.0100, -0.0110, 0.0219],
- [ 0.0478, -0.0192, 0.0139, ..., -0.0039, -0.0197, 0.0475]]
// 待更
// 待更
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。