MNN 介绍
本文将介绍MNN框架的后端支持,并介绍后端有关的核心代码与功能。
1. 后端
MNN是阿里巴巴开源的高效轻量级深度学习推理框架,提供了较为全面的后端支持。 主要包括:
| 架构/精度 |
类型 |
Normal |
FP16 |
BF16 |
Int8 |
| CPU |
Native |
B |
C |
B |
B |
| CPU |
x86/x64-SSE4.1 |
A |
C |
C |
A |
| CPU |
x86/x64-AVX2 |
S |
C |
C |
A |
| CPU |
x86/x64-AVX512 |
S |
C |
C |
S |
| CPU |
ARMv7a |
S |
S(ARMv8.2) |
C |
S |
| CPU |
ARMv8 |
S |
S(ARMv8.2) |
S(ARMv8.6) |
S |
| GPU |
OpenCL |
A |
S |
C |
S |
| GPU |
Vulkan |
A |
A |
C |
A |
| GPU |
Metal |
A |
S |
C |
S |
| GPU |
CUDA |
A |
S |
C |
A |
| NPU |
CoreML |
A |
C |
C |
C |
| NPU |
HIAI |
A |
C |
C |
C |
| NPU |
NNAPI |
B |
B |
C |
B |
| NPU |
QNN |
C |
B |
C |
C |
- S(Support and work well):深度优化,推荐使用
- A(Support and work well):支持良好,可以使用
- B(Support but has bug or not optimized):支持但有bug或未优化,不推荐使用
- C(Not Support):不支持
1.1 CPU后端系列
1.1.1 x86/x64-SSE4.1后端
技术背景:
SSE4.1(Streaming SIMD Extensions 4.1)是Intel在2007年推出的SIMD指令集扩展,提供了47条新指令,主要用于加速多媒体和浮点运算。
适用设备:一般是较老CPU
- Intel Core系列处理器(2008年后)
- AMD处理器(部分支持)
- 服务器和桌面PC
- 部分笔记本电脑
技术特点:
- 128位SIMD寄存器
- 支持打包整数和浮点运算
- 提供点积、混合、提取和插入操作
1.1.2 x86/x64-AVX2后端
技术背景:
AVX2(Advanced Vector Extensions 2)是Intel在2013年推出的256位SIMD指令集,是AVX的增强版本,提供了更宽的向量寄存器和更多的指令。
适用设备:主流CPU
- Intel Haswell架构及以后的处理器
- AMD Excavator架构及以后的处理器
- 现代服务器和高性能工作站
- 游戏PC和高端笔记本
1.1.3 x86/x64-AVX512后端
技术背景:
AVX512是Intel最新的512位SIMD指令集,首次出现在Xeon Phi处理器中,后来扩展到Xeon和Core系列。
适用设备:主流高性能CPU
- Intel Xeon Scalable处理器
- Intel Core i7/i9高端处理器(部分型号)
- 高性能计算服务器
- AI训练和推理专用硬件
1.1.4 ARMv7a后端
技术背景:
ARMv7-A是ARM公司的32位架构,广泛应用于早期智能手机和嵌入式设备。支持NEON SIMD指令集。
适用设备:一般是比较老旧的移动端CPU
- 早期Android手机(2010-2015年)
- Raspberry Pi 2
- 部分嵌入式开发板
- 工业控制设备
1.1.5 ARMv8后端
技术背景:
ARMv8-A是ARM的64位架构,是现代移动设备的主流架构,提供了更强的性能和更大的内存寻址空间。
适用设备:当前主流的移动端CPU
- 现代智能手机(iPhone 5s以后,Android旗舰机)
- 平板电脑
- ARM服务器(如AWS Graviton)
- Apple M系列芯片设备
- Raspberry Pi 3/4
1.2 GPU后端系列
1.2.1 OpenCL后端
技术背景:
OpenCL(Open Computing Language)是Khronos Group制定的开放标准,用于异构计算平台的并行编程。
适用设备:除专用的GPU后端外,主流的GPU后端
- 支持OpenCL的GPU(NVIDIA、AMD、Intel)
- 部分移动GPU(Adreno、Mali、PowerVR)
- FPGA设备
- DSP处理器
1.2.2 Vulkan后端
技术背景:
Vulkan是Khronos Group开发的低开销、跨平台的3D图形和计算API,提供了更直接的GPU控制。
适用设备:大部分GPU支持的后端
- 现代GPU(NVIDIA GTX 900系列以后)
- AMD GCN架构GPU
- Intel集成显卡(部分支持)
- Android设备(API 24+)
技术背景:
Metal是Apple开发的低级图形和计算API,专为Apple设备优化,提供了接近硬件的性能。
适用设备:ios设备的GPU后端
1.2.4 CUDA后端
技术背景:
CUDA(Compute Unified Device Architecture)是NVIDIA开发的并行计算平台和编程模型,专为NVIDIA GPU设计。
适用设备:NVIDIA GPU的专用后端
- NVIDIA GeForce系列GPU
- NVIDIA Quadro专业卡
- NVIDIA Tesla计算卡
- NVIDIA Jetson嵌入式平台
1.2.5 OpenGL后端
技术背景:OpenGL(Open Graphics Library)是一个图形渲染 API 标准,可以利用 GPU 并行计算能力处理非图形任务
适用设备:大部分GPU支持的后端
- 支持OpenCL的GPU(NVIDIA等)
- 部分移动GPU(Adreno等)
1.3 专用加速器
1.3.1 CoreML后端
技术背景:
Core ML是Apple的机器学习框架,能够利用Apple设备的Neural Engine进行AI推理加速。
适用设备:当前主流的apple ai加速器
- iPhone(A11芯片以后,支持Neural Engine)
- iPad Pro(A12芯片等)
- Mac(M1芯片等)
1.3.2 HIAI后端
技术背景:
HIAI(Huawei Intelligent Acceleration Infrastructure)是华为开发的AI加速平台,利用华为麒麟芯片的NPU。
适用设备:当前主流的华为 ai加速器
- 华为/荣耀手机(麒麟970以后)
- 华为平板电脑
- 华为笔记本电脑(部分型号)
1.3.3 NNAPI后端
技术背景:
NNAPI(Neural Networks API)是Google在Android 8.1中引入的API,为Android设备提供统一的AI加速接口。
适用设备:在 Android 15 中已弃用。在专用供应商驱动程序的Android设备运行,缺乏时运行在 CPU 上
1.3.4 QNN后端
技术背景:
QNN(Qualcomm Neural Network SDK)是高通开发的AI推理SDK,专为高通Snapdragon平台优化。
适用设备:主流的高通芯片NPU后端
- 搭载Snapdragon处理器的设备
- 支持Hexagon DSP的设备
- 高通AI开发套件
1.3.5 NeuroPilot 后端
技术背景:
NeuroPilot SDK是联发科自研的专用 AI 加速硬件单元SDK,集成于天玑(Dimensity)系列 SoC 中。主要通过 NNAPI 间接调用 APU 硬件加速
适用设备:主流的天玑芯片APU后端
- 搭载天玑系列处理器的设备
- 支持 NeuroPilot SDK 且 Android 10+(API 29+)的联发科平台设备
1.4 后端相关目录结构
MNN的后端实现集中在source/backend/目录中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
source/backend/
├── cpu/ # CPU通用后端
| └───x86_x64
| └─── avx/ # x86 AVX2优化
| └─── avx512/ # x86 AVX512优化
| └─── avxfma/ # x86 AVXfma 支持Fused Multiply-Add
| └─── sse/ # x86 SSE优化
| └─── arm
| └───arm32 # 32为arm指令支持 如ARMv7-A后端
| └───arm64 # 64为arm指令支持
├── arm82/ # ARMv8.2 支持
| └─── asm
| └───arm32 # 32为arm指令兼容
| └───arm64 # 64为arm指令支持
├── nnapi/ # NNAPI后端
├── metal/ # Metal后端(iOS/macOS)
├── opencl/ # OpenCL后端
├── opengl/ # OpenGL后端
├── vulkan/ # Vulkan后端
├── cuda/ # CUDA后端
├── tensorrt
├── coreml/ # CoreML后端
├── hiai/ # HIAI后端
└── qnn/ # QNN后端
└── neuropilot/ # neuropilot后端
|
相应的后端需要设置指定的编译宏,例如-DMNN_OPENCL=true表示启用OPENCL后端支持,但是具体调用需要在程序中设置,例如MNN-LLM需要设置… 具体见后续的代码梳理
1.4.1 后端文件说明
MNN 的后端系统由以下几个核心类组成,大致执行顺序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 1. 创建 下面的关系只表示时间上的顺序依赖关系 如Backend需要调用Runtime创建
Runtime (运行时, 各类后端继承Runtime, 如class CPURuntime : public Runtime)
↓ 创建
Backend (后端, 各类后端继承Backend, 如class CPUBackend : public Backend)
↓ 创建
Execution (执行器, 大部分算子实现是通过继承Execution实现,如:class CPUAttention : public Execution;
小部分为汇编优化过的代码, 如source/backend/cpu/x86_x64/avx512/_AVX512_MNNGemmFloatUnit16x8.S)
// 2. 重新计算输出形状 在/workspace/code/MNN/source/shape/SizeComputer.hpp内完成
// 3. 根据输入长度变化 执行的resize操作 下面的关系只表示时间上的顺序依赖关系
Backend::onResizeBegin (resize的准备阶段)
↓
Execution::onResize (算子的resize)
↓
Backend::onResizeEnd (resize的收尾阶段)
// 4. 执行算子 下面的关系只表示时间上的顺序依赖关系
Backend::onExecuteBegin (execute的准备阶段)
↓
Execution::onExecute (算子的execute)
↓
Backend::onExecuteEnd (execute的收尾阶段)
|
1.4.1.1 Runtime - 运行时抽象层
位置:source/core/Backend.hpp
Runtime 是硬件运行时的抽象层,负责管理整个后端的生命周期和资源配置。
核心职责:
- 创建 Backend 实例
- 管理线程池和内存分配器
- 提供垃圾回收机制
- 支持异步执行和并发控制
关键接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Runtime : public NonCopyable {
public:
// 创建 Backend
virtual Backend* onCreate(const BackendConfig* config, Backend* origin) const = 0;
// 重置运行时配置
virtual void onReset(int numberThread, const BackendConfig* config, bool full);
// 垃圾回收
virtual void onGabageCollect(int level) = 0;
// 获取内存使用量
virtual float onGetMemoryInMB();
// 主要是cpuruntime使用 cpu后端实现了线程池(source/backend/cpu/ThreadPool.hpp)
virtual void onConcurrencyBegin() const = 0;
virtual void onConcurrencyEnd() const = 0;
// 执行优化, mnn以算子图形式运行 下面是不同的运行方式
enum CompilerType {
Compiler_Geometry = 0, // 部分执行几何计算,分解形变算子,但不分解 BatchMatMul / Gather 等算子
Compiler_Origin = 1, // 直接使用原始算子,不进行分解
Compiler_Loop = 2, // 完全执行几何计算,仅此模式下,可以在算子不支持时自动回退到CPU计算
};
private:
// 记录运行时信息 如kvcacheSizeLimit cpuIds等
RuntimeHint mHint;
};
|
1.4.1.2 Backend - 后端抽象基类
位置:source/core/Backend.hpp
Backend 是所有硬件后端的基类,定义了后端必须实现的接口。
核心职责:
- 管理内存分配和释放
- 创建 Execution
- 处理张量缓冲区操作
- 支持多种存储策略
存储类型枚举:
1
2
3
4
5
6
|
enum StorageType {
STATIC, // 不可重用内存,分配后立即释放
DYNAMIC, // 可重用内存,优先重用已有内存
DYNAMIC_SEPERATE, // 不可重用内存,但延迟释放
DYNAMIC_IN_EXECUTION // 执行时动态分配
};
|
关键接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Backend : public NonCopyable {
public:
// 创建 Execution
virtual Execution* onCreate(const std::vector<Tensor*>& inputs,
const std::vector<Tensor*>& outputs,
const MNN::Op* op) = 0;
// 执行算子Resize 的开始/结束的一些准备/收尾工作
virtual void onResizeBegin();
virtual ErrorCode onResizeEnd() = 0;
// 执行算子execute 的开始/结束的一些准备/收尾工作
// 例如OpenCLBackend中
virtual void onExecuteBegin() const = 0;
virtual void onExecuteEnd() const = 0;
// 内存管理
virtual MemObj* onAcquire(const Tensor* tensor, StorageType storageType) = 0;
virtual bool onClearBuffer() = 0;
virtual void onCopyBuffer(const Tensor* srcTensor, const Tensor* dstTensor) const = 0;
// 把数据映射到指定后端 / 从指定后端映射回数据
virtual void* onMapTensor(Tensor::MapType mtype, Tensor::DimensionType dtype, const Tensor* srcTensor);
virtual bool onUnmapTensor(Tensor::MapType mtype, Tensor::DimensionType dtype, const Tensor* dstTensor, void* mapPtr);
// 利用backend后端传递指针, llm中主要传递kvmeta(kv cache有关信息)
void setMetaPtr(void* ptr);
private:
// 枚举类 记录执行后端 如cpu opencl为: MNN_FORWARD_CPU MNN_FORWARD_OPENCL
const MNNForwardType mType;
};
|
1.4.1.3 Execution - 执行器抽象基类
位置:source/core/Execution.hpp
Execution 是算子执行的抽象基类,每个算子都有对应的 Execution 实现。
核心职责:
- 响应输入输出张量的形状变化
- 执行算子计算
- 支持克隆和共享权重
关键接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Execution : public NonCopyable {
public:
// 根据输入seq_len长度变化 调整执行时用到的值
virtual ErrorCode onResize(const std::vector<Tensor*>& inputs,
const std::vector<Tensor*>& outputs);
// 执行计算
virtual ErrorCode onExecute(const std::vector<Tensor*>& inputs,
const std::vector<Tensor*>& outputs) = 0;
// 克隆执行器 这在llm推理中很有用 例如prefill和decode之间的seq_len输入长度不一样 就会触发克隆 mnn默认保存不同输入长度的计算图
// 这里通常不克隆权重, 权重在Op* op中 这里会通过指针引用
virtual bool onClone(Backend* bn, const Op* op, Execution** dst);
// 获取后端 例如在CPUAttention中利用这里获取kvmeta信息(mMeta = (KVMeta*)(backend->getMetaPtr());)
Backend* backend() const;
};
|
1.4.2 后端调用设置
除了后端架构中的 Runtime、Backend、Execution 等核心类,MNN中还有部分特定基础算子的优化代码,例如source/backend/cpu/x86_x64/avx512/_AVX512_MNNGemmFloatUnit16x8.S 等 ,前者是汇编代码,后者是opencl的代码。
这里指的算子一般都是底层支持的基础算子,会在更上层的算子中被调用,例如CPUAttention中会调用core->Int8GemmKernel;以使用int8矩阵乘算子
1.4.2.1 Core Function
下面以CPU的后端在实例化过程中调用的不同后端的指令集为例说明,在source/backend/cpu/compute/CommonOptFunction.h中定义了CoreFunctions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
struct CoreFunctions {
// CPU 特性标志
bool supportFp16arith = false; // 支持 FP16 算术运算
bool supportSDot = false; // 支持 ARM S-Dot 指令
bool supportI8mm = false; // 支持 ARM I8MM 指令
bool supportSME2 = false; // 支持 ARM SME2 指令
int smeCoreNumber = 0; // SME2 核心数量
// 矩阵乘法相关函数
void(*MNNGetMatMulPackMode)(int* eP, int *lP, int* hP);
void(*MNNPackC4ForMatMul_A)(float* destOrigin, float const** sourceGroup,
const int32_t* info, const int32_t* el);
void(*MNNPackForMatMul_B)(float* dest, const float* source,
size_t h, size_t kernelsize, size_t ic, bool transpose);
void(*MNNPackedMatMul)(float* C, const float* A, const float* B,
const size_t* parameter, const float* postParameters,
const float* bias, const float* k, const float* b);
void(*MNNPackedMatMulRemain)(float* C, const float* A, const float* B,
size_t eSize, const size_t* parameter,
const float* postParameters, const float* bias,
const float* k, const float* b);
// 激活函数
void(*MNNReluInt8)(int8_t* dst, const int8_t* src, size_t size, ssize_t zeroPoint);
void(*MNNHardSwish)(float* dst, const float* src, size_t size);
void(*MNNGelu)(float* dst, const float* src, size_t size, float* parameters);
const float *beta, float epsilon, size_t size, bool RMSNorm);
// 其他函数...
};
|
MNN 使用全局单例 gCoreFunction 来存储当前平台的最优函数实现,在程序启动时会选择各个函数的最优实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// 在source/backend/cpu/CPUBackend.cpp 中创建runtime时 会进入到不同指令集的初始化注册过程
{
#ifdef MNN_SUPPORT_BF16
extern void registerBF16Backend();
#endif
#ifdef ENABLE_ARMV82
extern void registerArm82RuntimeCreator();
#endif
void registerCPURuntimeCreator() {
MNNCoreFunctionInit();
CPUBackend::initCreatorMap();
registerCPUOps();
#ifdef MNN_SUPPORT_BF16
registerBF16Backend();
#endif
#ifdef MNN_USE_ARMV82
registerArm82RuntimeCreator();
#endif
// TODO: Merge _initCoreFunction MNNFunctionInit and cpuinfo_arm_init
MNNInsertExtraRuntimeCreator(MNN_FORWARD_CPU, new CPURuntimeCreator);
}
// 下面以armv82的初始化为例 在Arm82Functions::init()中
Arm82Functions::init(){
// 部分特殊优化的底层代码
FUNC_PTR_ASSIGN(gInstance->MNNPackC4ForMatMul_A, Arm82MNNPackForMatMul_A);
/* 在声明中 这个代码是外部导入的, 路径位置source/backend/arm82/asm/arm64/Arm82MNNPackForMatMul_A.S
extern "C" {
// (UP_DIV(l,8), e, 8) -> (UP_DIV(e,eP), l, eP)
void Arm82MNNPackForMatMul_A(float* destOrigin, float const** sourceGroup, const int32_t* info, const int32_t* el);
}
*/
// 其它代码...
}
|
在cpu后端使用时通过调用后端的core function执行底层算子,如
1
2
3
4
5
6
7
8
9
10
11
|
// source/backend/cpu/CPUAttention.cpp
ErrorCode CPUAttention::onExecute(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs) {
auto gcore = static_cast<CPUBackend *>(backend())->functions();
auto core = static_cast<CPUBackend*>(backend())->int8Functions();
// 其它代码...
// 调用后端的core functions终端矩阵乘法
gcore->MNNPackedMatMul(...);
// 其它代码...
}
|
其它后端的底层代码调用可能不同,如opencl后端的.cl代码source/backend/opencl/execution/cl/attention_buf.cl等 通过运行时加载 Kernel 源码方式使用。