对于专家剪枝,我们参考了ACL 2024论文《Not All Experts are Equal: Efficient Expert Pruning and Skipping for Mixture-of-Experts Large Language Models》中的方案。具体来说,论文首先获取每一层Transformers Decoder的输出输出对并暂存。对于每一层的输入输出对,论文试图遍历该层中特定数量的路由专家组合,依次使用这些专家组合和输入重新获取新的输出,并通过重建损失找到和原输出最为相近的新输出对应的专家组合,最后在权重文件中仅保留该专家组合中的专家。在单个Transformer Decoder中的剪枝过程表示如下:
$
\min_C||\mathcal F’(x,C)-\mathcal F’(x,C_0)||
$
其中,x表示该层的输入,C表示专家组合,C_0表示单层Transformer Decoder中的所有路由专家,F(x,C)表示使用专家组合C将输入x进运算后得到的该层输出。论文由从低至高的顺序依次对每个Transformer Decoder进行专家剪枝。
动态大模型的端侧低内存部署技术 技术报告
本项目为“动态大模型的端侧低内存部署技术”赛题的代码和文档仓库。本文档描述了项目的技术细节,并给出了在一个通用任务上的评估指标。
0 总体技术路线
在本项目启动阶段,对DeepSeek-MoE-16B-base模型进行了如下观察:
因此,本项目首先使用面向专家模块的剪枝技术,对模型中的专家数量进行削减,以保证在长上下文或输出较长的场景中模型推理参数量有所降低。其次,项目对模型权重进行量化,提高推理速度的同时降低权重加载负担。最后,本项目实现了动态加载模块,使得模型在单次请求时的内存占用量保持在要求范围内。
1 项目仓库结构
expert_wise
文件夹包含了面向专家模块的剪枝相关代码,具体操作请参考文件夹内的README.md
文件;layer_wise
文件夹包含了面向Decoder层的剪枝相关代码,项目曾对两种剪枝技术进行MMLU-Pro Accuracy对比,最终选取了面向专家模块的剪枝技术。相关内容请参见下文。demo.py
文件提供了动态加载模型的示例启动代码。merge.py
文件提供了将量化权重与(剪枝后)模型权重融合的代码,融合后的模型权重文件可直接通过transformers库导入运行,但暂不支持动态加载。README-developer.md
中提供了执行量化的指令。2 面向专家模块的剪枝
对于专家剪枝,我们参考了ACL 2024论文《Not All Experts are Equal: Efficient Expert Pruning and Skipping for Mixture-of-Experts Large Language Models》中的方案。具体来说,论文首先获取每一层Transformers Decoder的输出输出对并暂存。对于每一层的输入输出对,论文试图遍历该层中特定数量的路由专家组合,依次使用这些专家组合和输入重新获取新的输出,并通过重建损失找到和原输出最为相近的新输出对应的专家组合,最后在权重文件中仅保留该专家组合中的专家。在单个Transformer Decoder中的剪枝过程表示如下: $ \min_C||\mathcal F’(x,C)-\mathcal F’(x,C_0)|| $ 其中,x表示该层的输入,C表示专家组合,C_0表示单层Transformer Decoder中的所有路由专家,F(x,C)表示使用专家组合C将输入x进运算后得到的该层输出。论文由从低至高的顺序依次对每个Transformer Decoder进行专家剪枝。
在实际运行中,我们发现该方法作用于DeepSeek-MoE模型的效率过低。我们注意到,原论文使用的剪枝模型为Mixtral-8x7B,该模型每层的专家数量为8,默认剪枝保留的专家数为2,因此共有28种选择,而DeepSeek-MoE-16B模型每层的路由专家数量为64,即使按原论文的参数保留2个路由专家,可能的组合也有2016种。为每种路由专家组合依次计算输出的策略极大增加了计算负担。
为此,我们提出使用贪心的策略为每层的路由专家进行剪枝。具体来说,在为每一层Transformer Decoder中的路由专家进行剪枝时,我们首先初始化一个保留专家列表,然后依次向该列表中添加一个路由专家,在获取输出后将其移出列表,添加新的路由专家。这样,我们收集了所有的单个路由专家的输出,选择输出和使用全部路由专家的输出最相近的一个路由专家,将其添加到保留专家列表中。然后,我们继续遍历剩余的路由专家,将其依次添加到列表中,计算输出后移出列表,从而得到使用两个路由专家的输出,并选择输出和使用全部路由专家的输出最相近的双路由专家的组合,将该组合保留专家列表中。我们不断重复上述步骤,直到列表中永久保留的路由专家数量达到设定值。通过这种方式,我们将保留两个路由专家的计算次数由2016次减少到64+63=127次,并使得我们可以尝试通过更少的计算成本,保留更多的路由专家。
具体实现时,我们基于原论文提供的开源代码,针对DeepSeek-MoE模型剪枝进行修改和优化。代码使用装饰器模式将单个MoE模块进行包装,在执行剪枝时,由装饰器中优化的
forward
方法执行MoE模块运算。我们获取MoEGate
模块输出的topk_weight
参数,然后根据保留专家列表,将不保留的专家对应的topk_weight
项置0,最后重新将topk_weight
归一化,使得各路由专家的权相加仍等于1。此外,我们遵循原论文设置,使用allenai/c4数据集的子集作为剪枝校准数据集。我们针对DeepSeek-MoE-16B模型结构跳过了第一层Transformer Decoder的剪枝,因为模型的第一层为MLP层。我们只对路由专家进行剪枝,而保留了所有共享专家。
最终,我们使用MMLU-Pro Accuracy作为指标,从每层保留16/24/28/32个路由专家的剪枝后模型中,选择了表现最好的每层保留24个路由专家的模型进行量化。
3 量化
在量化部分,我们参考了PMLR 2024发表的一篇论文《BiLLM: Pushing the Limit of Post-Training Quantization for LLMs》,这篇论文提出了一种名为 BiLLM 的 1 位后训练量化方案,用于预训练的大型语言模型(LLMs)。该方案基于 LLMs 的权重分布,通过识别和结构选择显著权重,并采用有效的二进制残差逼近策略来最小化压缩损失。具体来说,BiLLM将模型权重分为显著权重和非显著权重。对于显著权重,利用 Hessian 矩阵评估参数的显著度,通过结构化搜索选择显著权重,并采用二进制残差逼近方法对其进行二值化,以在精度和存储节省之间取得平衡。利用 Hessian 矩阵评估参数的显著度,通过结构化搜索选择显著权重,并采用二进制残差逼近方法对其进行二值化,以在精度和存储节省之间取得平衡。
在这项工作的开源代码中,对模型进行量化并不会直接保存为1bit量化后的模型权重,在评估1bit量化后的模型性能时,论文将其在量化后随即反量化到原有精度进行评估,因此这项工作实际上并不能直接在1bit位上进行计算减少模型推理时的内存占用。但这不代表对我们的目标完全没有帮助。参考赛题说明中提到的,当前的瓶颈在于“存储的IO性能与内存性能存在明显差异,不当的IO策略会严重拖慢推理速度”,我们的方案使用1bit量化后的权重代替原始权重文件,可以大幅度的减少权重文件的大小,进而提高从磁盘中加载模型权重的I/O速度。具体来说,我们参考BiLLM开源代码,对其进行修改,保存量化后的模型权重并将其拆分为单个权重文件,方便动态加载时加载制定的专家权重。这部分保存的内容除了将fp16参数量化到1bit的张量矩阵外,还包括用于反量化的掩码矩阵和系数。在推理阶段,加载权重后根据掩码和系数对模型进行反量化再参与计算。
4 动态加载
4.1 模型推理框架的改进
在 Python 中,几乎所有实体(函数、类、模块等)都是对象。这一特性赋予了开发者极大的灵活性,使得对象可以在运行时进行动态操作、赋值和修改,而不需要静态地改变源代码结构。
基于此,在实现 Huggingface 模型改进时,我们采用装饰器模式,基于原始的模型文件(位于models/deepseek/raw目录下)创建了独立的 configuration_deepseek_split.py 和 modeling_deepseek_split.py 文件(位于models/deepseek/variant目录下),动态加载和替换其中的对象,而不是直接修改原始代码。这样的实现具有以下的优势:
具体来说,该部分有以下几个改进之处:
4.2 专家缓存的设计
在 moe/cache.py 文件中,AbstractMoECache 提供了一个缓存管理的抽象接口,而 RandomQuantityCache 和 RandomMemoryCache 则分别实现了不同的缓存策略。这种策略模式的设计使得在不同场景下灵活地选择适合的缓存实现。抽象基类 AbstractMoECache 定义了缓存管理的基本接口,包括如何获取(get)、存储(_put)、移除(_evict)、以及检查缓存是否达到上限(_reach_limit)。具体缓存策略又包括 RandomQuantityCache 和 RandomMemoryCache。其中,RandomQuantityCache 是基于固定容量的缓存策略;RandomMemoryCache 是基于内存使用量限制的缓存策略,通过使用 psutil 库监控当前进程的内存使用情况。当内存达到上限时,它会随机移除某个缓存项,且通过 gc.collect() 强制进行垃圾回收以释放内存。
4.3 专家加载机制的设计
在 moe/expert.py 文件中,AbstractExpertLoader 提供了一个抽象的接口,而其不同的实现类 则代表了不同的专家权重加载策略。这些类实现了不同的权重加载方式,允许系统根据配置或需求动态选择使用哪一种权重加载策略。具体到实现类,DeepSeekMoESplitExpertLoader、DeepSeekMoELoraExpertLoader 和 DeepSeekMoEQuantExpertLoader 分别使用了不同的模型权重来源以及权重解析方式来加载:普通权重、LoRA 权重和量化权重,这些策略可以根据需求选择并扩展。
5 总体评估概述
本报告旨在评估一个经过剪枝和量化优化后的Mixture-of-Experts (MoE) 模型在SST-2(Stanford Sentiment Treebank)数据集上的表现。SST-2是一个广泛使用的情感分析任务数据集,包含正面和负面情感分类。为了提升模型的推理效率,我们对MoE模型进行了剪枝和量化操作,目标是降低模型的计算成本和存储需求,同时尽量保持其在分类任务中的准确性。
5.1 模型优化背景
通过剪枝和量化,我们希望在保证准确率的前提下,显著降低MoE模型的推理成本。
5.2 数据集与任务
5.3 实验设置
5.4 实验步骤
5.4.1 模型加载与预处理
5.4.2 模型训练与验证
5.4.3 测试集评估
5.5 实验结果
5.5.1 准确率和F1分数
5.5.2 推理效率
5.6 分析与讨论
从实验结果可以看出,剪枝和量化后的MoE模型在准确率和F1分数上有所下降,但性能下降幅度相对较小(准确率下降约6.3%,F1分数下降约5.4%)。然而,剪枝和量化显著降低了模型的推理时间(减少了约50%)和内存大小(减少了约50%)。这表明,在资源受限的环境中,优化后的MoE模型能提供更好的推理效率,同时保持较高的分类性能。
5.6.1 优化效果
5.6.2 影响分析
5.7 结论
剪枝和量化对样例MoE模型在SST-2情感分析任务中的准确率影响较小,但显著提升了模型的推理效率和存储效率。对于需要在资源受限环境中进行部署的应用,剪枝与量化技术是一种有效的优化手段。未来的工作可以进一步优化剪枝与量化策略,或探索在更大规模的数据集上应用这些技术的效果。