ncnn 的 MLIR 前端工具 mlir2ncnn:从源码编译到模型转换的完整指南
【免费下载链接】ncnnncnn is a high-performance neural network inference framework optimized for the mobile platform项目地址: https://gitcode.com/gh_mirrors/nc/ncnn
本文聚焦 ncnn 仓库中负责把 TensorFlow(含 Keras 风格)模型转换到 ncnn 格式的 MLIR 前端工具mlir2ncnn,完整讲解其从零编译(LLVM/MLIR 依赖 + 工具本体)、工作原理解析到实际使用的全流程。读完本文,你将能够独立在本机构建出mlir2ncnn可执行文件,并把导出的.mlir计算图转换为可直接被 ncnn 加载推理的.param/.bin模型文件,同时理解这条转换链路的内部实现细节。
mlir2ncnn 是什么
mlir2ncnn 是 ncnn 工具链中的模型转换前端之一,位于仓库 tools/mlir 目录。与依赖 protobuf 解析 TensorFlow GraphDef 的转换器不同,mlir2ncnn 以 LLVM 生态的MLIR(Multi-Level Intermediate Representation)为中间表示框架:它读取 TensorFlow 导出的.mlir文本文件,在 MLIR 上下文中加载 TensorFlow 方言(tf)与 ncnn 自研方言(ncnn),执行优化 pass 与模式重写(pattern rewrite),最终把计算图降级输出为 ncnn 的.param结构描述文件与.bin权重二进制文件。
从目录内容(tools/mlir/CMakeLists.txt)可以看到它由三部分支撑:
tf_dialect.cpp/tf_ops.td/tf_types.cc等:TensorFlow 方言定义与算子表;ncnn_dialect.cpp/ncnn_ops.td:ncnn 方言定义,注册KerasConv2D、KerasDense、KerasBatchNorm、BinaryOp、InstanceNorm、Swish等算子(见 ncnn_ops.td);ncnn_rewriter.cpp/ncnn_rewriter.td:把 TF 算子组合折叠为 ncnn 方言算子的重写规则;mlir2ncnn.cpp:主程序,负责解析、优化并输出 ncnn 模型文件。
编译 mlir2ncnn
mlir2ncnn 依赖 LLVM 的 MLIR 子项目,因此编译分两步:先编译 MLIR 并安装,再编译 mlir2ncnn 本体。
1. 克隆 LLVM 项目并切换到可用版本
mlir2ncnn 的编译对 MLIR 的 API 版本敏感,官方文档明确给出了一个经过验证的可用 commit:
git clone https://github.com/llvm/llvm-project.git git checkout -b mlir <a_working_commit_id>当前可用的 commit id 为74e6030bcbcc8e628f9a99a424342a0c656456f9:
$ git log commit 74e6030bcbcc8e628f9a99a424342a0c656456f9 (HEAD -> main, origin/main, origin/HEAD) Author: Craig Topper <craig.topper@sifive.com> Date: Thu Mar 4 22:30:38 2021 -0800 [TargetLowering] Use HandleSDNodes to prevent nodes from being deleted by recursive calls in getNegatedExpression.该 commit 的选取原则是:以tools/mlir目录最近一次提交日期为基准,选择一个与之时间匹配的 LLVM 上游提交。如果使用过新或过旧的 MLIR,可能出现 API 不兼容导致编译失败,此时应回到该 commit 附近的版本重试。
2. 编译并安装 MLIR
进入 llvm-project 后,建议使用 Ninja 构建系统(需提前安装 ninja-build):
cd llvm-project mkdir build cd build cmake -G Ninja -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DLLVM_ENABLE_PROJECTS="mlir" -DLLVM_TARGETS_TO_BUILD="" -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF ../llvm/ ninja -j8 ninja install关键 CMake 参数含义:
| 参数 | 取值 | 说明 |
|---|---|---|
-G Ninja | Ninja | 使用 Ninja 作为生成器,加速并行构建 |
-DCMAKE_INSTALL_PREFIX=install | 相对路径 | MLIR 的安装前缀,后续 mlir2ncnn 编译需要引用它 |
-DCMAKE_BUILD_TYPE=Release | Release | 使用优化构建,显著减少工具运行时间 |
-DBUILD_SHARED_LIBS=ON | ON | 构建共享库,减少链接体积与时间 |
-DLLVM_ENABLE_PROJECTS="mlir" | mlir | 只启用 MLIR 子项目 |
-DLLVM_TARGETS_TO_BUILD="" | 空 | 不构建任何后端目标,加快编译(转换器不需要 LLVM 代码生成后端) |
-DLLVM_INCLUDE_EXAMPLES=OFF/-DLLVM_INCLUDE_TESTS=OFF | OFF | 跳过示例与测试,进一步缩短编译时间 |
ninja install后,MLIR 会安装到<llvm-project>/build/install目录下,其中包含 mlir2ncnn 编译所需的lib/cmake/llvm与lib/cmake/mlir两个 CMake 包目录。
3. 编译 mlir2ncnn
cd tools/mlir mkdir build cd build cmake .. -D LLVM_DIR=<path/to/your/llvm_install/lib/cmake/llvm> make这里的<path/to/your/llvm_install>就是上一步CMAKE_INSTALL_PREFIX指定的安装目录,例如llvm-project/build/install。完成后会在当前目录生成mlir2ncnn可执行文件。
注意:
tools/mlir有自己独立的CMakeLists.txt(tools/mlir/CMakeLists.txt),并不随 ncnn 主工程的tools/CMakeLists.txt一起构建,因此必须如上单独进入该目录编译。
编译细节与源码佐证
从 tools/mlir/CMakeLists.txt 可以看到 mlir2ncnn 的构建并不平凡,它需要:
- 通过
find_package(LLVM REQUIRED)与find_package(MLIR REQUIRED)定位安装好的 LLVM/MLIR 包(默认前缀LLVM_PROJECT_INSTALL_DIR指向/home/nihui/osd/llvm-project/build/install,编译时应用-DLLVM_PROJECT_INSTALL_DIR=覆盖为你自己的安装路径); - 启用 TableGen 生成三组头文件:
tf_ops.td→tf_all_ops.h.inc/tf_all_ops.cc.inc(TF 算子声明与定义);ncnn_ops.td→ncnn_ops.h.inc/ncnn_ops.cc.inc(ncnn 方言算子);ncnn_rewriter.td→ncnn_rewriter.inc(自动生成的模式重写规则);
- 链接 MLIR 的
MLIRIR、MLIRDialect、MLIRInferTypeOpInterface、MLIRParser、MLIRPass、MLIRStandard、MLIRTransforms等库,并以-fno-rtti -fno-exceptions编译,与 LLVM 的编译约定保持一致; - 最终通过
ncnn_install_tool(mlir2ncnn)把工具安装到 ncnn 的二进制目录。
这也是为什么编译 mlir2ncnn 必须匹配特定版本的 MLIR——它直接依赖 MLIR 的 Pass、Parser、Dialect 等内部 API。
使用 mlir2ncnn 转换模型
第一步:导出.mlir文件
mlir2ncnn 的输入是 TensorFlow 模型经 MLIR 前端(如tf-mlir-translate等)导出的.mlir文本文件。导出时得到的计算图应包含tf.Placeholder(输入)、tf.Const(权重)、tf.Conv2D、tf.MatMul、tf.Relu等标准 TF 算子,并以名为main的func作为入口——这一点可以从 mlir2ncnn 源码中得到印证:它在解析后通过m->lookupSymbol<mlir::FuncOp>("main")查找main函数(mlir2ncnn.cpp)。
第二步:运行 mlir2ncnn
转换命令的用法为:
./mlir2ncnn pix2pix.mlir pix2pix.param pix2pix.bin三个参数依次为:输入.mlir文件、输出的 ncnn.param结构文件、输出的 ncnn.bin权重文件。
源码(mlir2ncnn.cpp 的main函数)还支持一种简写形式:只传入一个.mlir参数时,输出文件名默认为ncnn.param与ncnn.bin:
./mlir2ncnn pix2pix.mlir # 等价于 ./mlir2ncnn pix2pix.mlir ncnn.param ncnn.bin转换成功后,得到的.param/.bin即可按 ncnn 常规方式加载推理。.param文件遵循 ncnn 的 param 文件格式(文件头为魔数7767517,首行输出即为该值,随后是层数与 blob 数),其格式细节可参考 param-and-model-file-structure.md。
mlir2ncnn 的转换原理
转换主流程
mlir2ncnn.cpp 的main函数完整呈现了转换主流程:
- 加载方言:在
MLIRContext中注册StandardOpsDialect(标准算子)、TensorFlowDialect(TF 算子)与NCNNDialect(ncnn 算子); - 解析输入:
parseSourceFile读取.mlir文件; - 优化 pass:
PassManager执行createNCNNOptimizePass()(ncnn 优化 pass),完成算子规范化与折叠; - 遍历
main函数:对基本块中的每个 operation 统计输入输出、收集权重与 blob 名、计算引用计数; - 输出 ncnn 文件:先写出
7767517魔数与[layer count] [blob count]头,再依次写出权重对应的MemoryData层、Split层(当某个 blob 被多处引用时)以及所有模型算子层; - 写出权重:把 TF 权重的内存布局重排为 ncnn 要求的布局后写入
.bin。
算子映射表
源码中把 TF / ncnn 方言算子映射为 ncnn 层类型,主要的映射关系如下表:
| MLIR 算子 | ncnn 层 |
|---|---|
tf.Placeholder | Input |
tf.Conv2D/ncnn.KerasConv2D | Convolution |
tf.Conv2DBackpropInput | Deconvolution |
tf.DepthwiseConv2dNative | ConvolutionDepthWise |
tf.MatMul(transpose_a=0 且 transpose_b=1) | InnerProduct |
tf.MatMul(其余情况) | Gemm |
ncnn.KerasDense/tf.BiasAdd折叠后的全连接 | InnerProduct |
ncnn.KerasBatchNorm | BatchNorm |
tf.AvgPool/tf.MaxPool | Pooling |
tf.Mean(keep_dims=0 且对轴 1、2 归约) | Pooling(全局均值池化) |
tf.Mean(其他情况) | Reduction |
tf.AddN | Eltwise |
tf.AddV2/tf.Sub/tf.Mul/tf.Maximum/tf.Minimum | BinaryOp |
tf.ConcatV2 | Concat |
tf.Relu/tf.LeakyRelu | ReLU |
tf.Relu6 | Clip |
tf.Reshape | Reshape |
tf.ResizeBilinear/tf.ResizeNearestNeighbor | Interp |
tf.Pad | Padding |
tf.StridedSlice | Crop |
tf.Softmax | Softmax |
tf.Sigmoid | Sigmoid |
tf.Tanh | TanH |
tf.DepthToSpace/tf.SpaceToDepth | PixelShuffle / Reorg |
tf.Identity/std.return | Noop |
tf.Const | 不输出为层,转为 MemoryData 权重 |
对于尚未支持的算子,源码会向 stderr 打印%s not supported yet!警告,并把原始算子名原样写入 param(按 TODO 处理),因此转换前建议确认模型只使用了上表覆盖的算子集合。
布局与轴序的转换(NHWC → NCHW)
TensorFlow 默认使用 NHWC 数据布局,而 ncnn 使用 NCHW。mlir2ncnn 在转换中统一做了布局转换,这也是源码中最具技术含量的部分:
- 卷积权重:TF 的
tf.Conv2D权重形状为kh-kw-inch-outch([kernel_h, kernel_w, in_channels, out_channels]),源码按o-i-h-w顺序重排后写入.bin,同时每个卷积权重前先写入一个int类型的 quantize_tag(当前恒为 0);深度卷积tf.DepthwiseConv2dNative的权重则按i-cm-h-w重排,并映射到ConvolutionDepthWise(group = 输入通道数); - 全连接权重:
tf.MatMul的i-o布局被重排为 ncnn 的o-i布局; - Padding 语义:TF 的
SAME被映射为 ncnn 的pad = -233(自动计算),VALID映射为pad = 0,EXPLICIT则按[[0,0],[pad_top,pad_bottom],[pad_left,pad_right],[0,0]]显式写出四个方向的 pad 值(对应 ncnn 的4/14/15/16参数); - Reshape / Concat / StridedSlice / Mean 的轴映射:例如
tf.ConcatV2的 4 维axis=3(通道维)会被映射为 ncnn 的axis=0,tf.Reshape的[n, h, w, c]被映射为 ncnn 的[w, h, c](即0=w 1=h 2=c); - Resize 语义:
tf.ResizeBilinear/tf.ResizeNearestNeighbor要求align_corners=0且half_pixel_centers=1,否则会打印Unsupported警告。
模式重写:把 TF 组合算子折叠为 ncnn 算子
在优化 pass 中,ncnn_rewriter.td 定义了一组 TableGen 重写规则,把常见的 TF 算子组合“折叠”为单个 ncnn 方言算子,例如:
tf.Mul(x, 标量Const)→ncnn.BinaryOp(op_type=2,乘);tf.AddV2(x, 标量Const)→ncnn.BinaryOp(op_type=0,加);BiasAdd(Conv2D(x, w), bias)→ncnn.KerasConv2D(卷积与偏置融合);BiasAdd(MatMul(x, w), bias)→ncnn.KerasDense(全连接与偏置融合);AddV2(Mul(x, gamma), bias)(非标量 gamma)→ncnn.KerasBatchNorm;- 由
Mean / SquaredDifference / Rsqrt等组合表达的 InstanceNorm 结构 →ncnn.InstanceNorm/ncnn.InstanceNormAffine; Mul(Sigmoid(x), x)→ncnn.Swish。
这些规则在编译期由 TableGen 生成(见 tools/mlir/CMakeLists.txt 中的ncnn_rewriterIncGen目标),在运行期由NCNNOptimizePass应用,是 mlir2ncnn 能把复杂 TF 计算图降级为紧凑 ncnn 图的核心机制。ncnn 方言的注册实现可进一步参阅 ncnn_dialect.cpp。
常见问题与注意事项
- MLIR 版本不匹配:mlir2ncnn 依赖特定时期的 MLIR API,若编译报错,优先切回文档给出的 commit
74e6030bcbcc8e628f9a99a424342a0c656456f9附近版本,并保证LLVM_DIR指向该版本安装的lib/cmake/llvm; - 必须单独构建:mlir2ncnn 不在 ncnn 主工程 CMake 的
add_subdirectory列表中,需要自行进入 tools/mlir 构建; - 输入图要求:
.mlir文件必须包含名为main的函数(源码通过lookupSymbol<FuncOp>("main")定位),且算子需位于支持的映射表内; - 输出文件格式:生成的
.param以7767517魔数开头,配合.bin即可被 ncnn 的Net::load_param/Net::load_model加载;如需进一步压缩或优化(如 int8 量化、算子融合),可继续使用仓库中的 ncnnoptimize 等工具。
小结
mlir2ncnn 展示了 ncnn 工具链中“以 MLIR 为中间表示做模型前端”的技术路线:通过自定义 TF 方言与 ncnn 方言、结合 TableGen 模式重写与布局转换 pass,把 TensorFlow 计算图完整降级为 ncnn 的.param/.bin模型。本文给出的编译命令(LLVM 版本锁定、MLIR 构建参数、mlir2ncnn 独立构建)与转换用法(./mlir2ncnn model.mlir model.param model.bin)均基于当前仓库 docs/how-to-build/build-mlir2ncnn.md 及 tools/mlir 源码整理,可直接复现验证。
【免费下载链接】ncnnncnn is a high-performance neural network inference framework optimized for the mobile platform项目地址: https://gitcode.com/gh_mirrors/nc/ncnn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考