MNN-LLM 正式合入
增加Transformer相关算子与对应图优化功能
- 编译 MNN 时打开宏 MNN_LOW_MEMORY 和 MNN_SUPPORT_TRANSFORMER_FUSE ,增加在线权重反量化、动态量化和Transformer相关算子支持
- -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_LOW_MEMORY=ON
- 转换模型时加上 --transformerFuse=1 开启Transformer模型相关图优化
新增 LLM 模块
使用方法
构建 LLM 文件包
- 下载模型,推荐使用魔搭
- pip install modelscope
- 执行脚本(以千问7b为例)
from modelscope import snapshot_download
from transformers import AutoModelForCausalLM, AutoTokenizer
# Downloading model checkpoint to a local dir model_dir
model_dir = snapshot_download('qwen/Qwen-7B-Chat')
#model_dir = snapshot_download("modelscope/Llama-2-7b-chat-ms", revision='v1.0.5',
ignore_file_pattern=[r'.+\.bin$'])
#model_dir = snapshot_download('qwen/Qwen-1_8B-Chat')
print(model_dir)
- 使用 MNN 目录下面的 transformers/llm/export/llm_export.py 进行模型导出,把 torch 模型转换成 onnx
- 示例
- python3 llm_export.py --embed_bin --embed_bf16 --onnx_path onnx --type Qwen-7B-Chat --path /Users/xtjiang/.cache/modelscope/hub/qwen/Qwen-7B-Chat --export_embed --export_token --mnn_path mnn --export
- 务必加 --embed_bin 参数,将 embeding 层导出成 bin 文件
- 完成后会产出两个文件夹
- onnx
- llm.onnx
- tokenizer.txt
- 其他外置权重数据,不用关注
- mnn
- embeddings_bf16.bin
- onnx
- 示例
- 使用 MNNConvert 转换 onnx 模型,并进行量化
- ./MNNConvert -f ONNX --modelFile onnx/llm.onnx llm.mnn --weightQuantBits=4 --transformerFuse=1 --saveExternalData
- 一般需要20-30 分钟,请耐心等待
- 产出 llm.mnn 与 llm.mnn.weight
- 建文件夹,组合上述步骤产出的文件,这里以千问7b为例
- 文件夹名:qwen-7b-int4
- 包含文件
- embeddings_bf16.bin
- llm.mnn
- llm.mnn.weight
- tokenizer.txt
编译LLM引擎并使用
-
编译MNN打开 MNN_BUILD_LLM 宏,编译 transformers/llm/engine 目录,产出 libllm 和 llm_demo
-
使用 llm_demo 运行 llm
- 参数:
- llm.mnn 文件路径
- forwardtype :0 为 CPU ,3 为 OpenCL
- Memory | Precision :Memory * 4 + Precision ,如果 memory 和 preicsion 都为 low ,即设为 10
- 示例:
- ./llm_demo ../../qwen-7b-int4/llm.mnn 0 10
- 【然后进行对话】
- ./llm_demo ../../qwen-7b-int4/llm.mnn 0 10 prompt.txt :使用预设的问题进行测试
- ./llm_demo ../../qwen-7b-int4/llm.mnn 0 10
- 参数:
-
CPU / GPU
性能测试
-
8Gen1
| model | CPU 4线程 | | OpenCL | |
| --- | --- | --- | --- | --- |
| | prefill | decode | prefill | decode |
| qwen-1.8b | 207.56 | 35.58 | 28.58 | 20.40 |
| qwen-7b | 25.86 | 7.5 | 7.95 | 7.70 |
| llama3-8b | 22.09 | 5.59 | 内存不足 | 内存不足 | -
8Gen3
| model | CPU 4线程 | | OpenCL | |
| --- | --- | --- | --- | --- |
| | prefill | decode | prefill | decode |
| qwen-1.8b | 205.70 | 47.07 | 61.25 | 21.56 |
| qwen-7b | 40.93 | 11.01 | 20.26 | 10.60 |
| llama3-8b | 36.44 | 7.83 | 19.10 | 2.14 | -
注:暂未对 llama3-8b 结构支持 Transformer 相关图优化,因此GPU性能较差
形变缓存机制
背景
对于语音/文本类的模型,往往涉及张量某个维度的逐步变化,这种情况下每次都会进行几何计算、申请内存等操作,导致不可忽略的性能损耗。考虑到输入单个维度变化的情况下,网络中会存在部分算子形状不变,这部分计算是可以去除的。
为了对这种情况进行优化,MNN新增了形变缓存机制,使模型输入形状变化时,形状不变的算子跳过形变相关操作,以提升性能。
原理
- MNN 通过两个状态去优化Resize过程,先在 Check 状态下寻找不变形状的算子,然后切换到 Fix 状态,分离易变形状与不变形状算子进行处理。对应于 Interpreter.hpp 里面增加两个 SessionMode
- Session_Resize_Check
- Session_Resize_Fix
- Check 状态
- 存储或检查输入的形状是否与上次一致,对应跳过几何计算阶段,但资源分配阶段不跳过
- Fix 状态
- 根据算子的形状不变标志,跳过几何计算与资源分配阶段
基于 Module API 使用
对应 API
- **Module::**traceOrOptimize
示例
- 网络输入为 speech ,输出为 out0 ,out1 ,输入大小为: 1,3,X
std::shared_ptr<MNN::Express::Module> net(MNN::Express::Module::load(fileName, {"speech"}, {"out0", "out1"}), MNN::Express::Module::destroy);
net->traceOrOptimize(MNN::Interpreter::Session_Resize_Check);
int validShape[] = {64, 112};
for (int i=0; i<2; ++i) {
auto varp = MNN::Express::_Input({1, 3, validShape[i]}, MNN::Express::NCHW);
varp->writeMap<float>();
auto outputs = net->onForward({varp});
}
net->traceOrOptimize(MNN::Interpreter::Session_Resize_Fix);
// Forward and use output
基于 Intrepreter - Session API使用
相关API
- Interpreter::setSessionMode
示例
- 网络输入为 speech ,输出为 out0 ,out1 ,输入大小为: 1,3,X
std::shared_ptr<MNN::Interpreter> net(MNN::Interpreter::createFromFile(fileName), MNN::Interpreter::destroy);
MNN::ScheduleConfig config;
/*Set up config
......
*/
MNN::Session* session = net->createSession(config);
auto input = net->getSessionInput(session, "speech");
auto out0 = net->getSessionOutput(session, "out0");
auto out1 = net->getSessionOutput(session, "out1");
net->setSessionMode(Interpreter::Session_Resize_Check);
/*Resize Session for each valid size Begin*/
/*Demo: */
net->resizeTensor(input, {1, 3, 112});
net->resizeSession(session);
net->resizeTensor(input, {1, 3, 64});
net->resizeSession(session);
/*Resize Session for each valid size End*/
net->setSessionMode(Interpreter::Session_Resize_Fix);
/*Fill input and run session
......
*/
鸿蒙系统支持、功能完善与Github Issue 修正
功能完善
- 支持华为鸿蒙系统上的编译与运行
- 参考 MNN/project/harmony 下面的脚本进行编译
- 增加 WinogradLevel 配置,支持用户设定较低的Winograd 输出计算尺寸 ,以节省内存
- setSessionHint(WINOGRAD_MEMORY_LEVEL, 0) :使用最低计算尺寸
- setSessionHint(WINOGRAD_MEMORY_LEVEL, 3) :使用默认计算尺寸
- 修正 onnx onehot 算子在输入尺寸未知时转换出错问题
- 修正 Sigmoid / Tanh 等算子量化后出错的问题
- 优化 Tile 算子的几何计算分解,降低产出的Region数,以减少对应GPU后端的初始化耗时
- 支持 LayerNorm RMS 模式及相应的图优化
- 原生支持onnx-external data ,降低 Onnx 大模型转换所需要内存
- Metal 后端 NC4HW4 布局模式修改为C4NHW4,以优化 batch > 1 的卷积性能
- Metal 后端支持 BatchMatMul / Gather / GatherND 等对应生成的 Loop 算子
- Metal 后端支持 GridSampler3D
- Metal 后端支持可变精度设置
- precision 为 low / normal 时使用 fp16 存储
- precision 为 high 时使用 fp32 存储
- 计算一律使用 fp32
- CPU 后端修正权值在线反量化模式下,输入输出通道不对齐时出错的问题
- OpenCL 后端优化 Pooling 算子在 KernelSize 很大时的性能问题
- OpenCL 后端增加 Kernel 缓存机制,降低部分设备上 resize 耗时
- 其他Bugfix与优化