
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 生态的MLIRMulti-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.tdncnn 方言定义注册KerasConv2D、KerasDense、KerasBatchNorm、BinaryOp、InstanceNorm、Swish等算子见 ncnn_ops.tdncnn_rewriter.cpp/ncnn_rewriter.td把 TF 算子组合折叠为 ncnn 方言算子的重写规则mlir2ncnn.cpp主程序负责解析、优化并输出 ncnn 模型文件。编译 mlir2ncnnmlir2ncnn 依赖 LLVM 的 MLIR 子项目因此编译分两步先编译 MLIR 并安装再编译 mlir2ncnn 本体。1. 克隆 LLVM 项目并切换到可用版本mlir2ncnn 的编译对 MLIR 的 API 版本敏感官方文档明确给出了一个经过验证的可用 commitgit 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.toppersifive.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-buildcd llvm-project mkdir build cd build cmake -G Ninja -DCMAKE_INSTALL_PREFIXinstall -DCMAKE_BUILD_TYPERelease -DBUILD_SHARED_LIBSON -DLLVM_ENABLE_PROJECTSmlir -DLLVM_TARGETS_TO_BUILD -DLLVM_INCLUDE_EXAMPLESOFF -DLLVM_INCLUDE_TESTSOFF ../llvm/ ninja -j8 ninja install关键 CMake 参数含义参数取值说明-G NinjaNinja使用 Ninja 作为生成器加速并行构建-DCMAKE_INSTALL_PREFIXinstall相对路径MLIR 的安装前缀后续 mlir2ncnn 编译需要引用它-DCMAKE_BUILD_TYPEReleaseRelease使用优化构建显著减少工具运行时间-DBUILD_SHARED_LIBSONON构建共享库减少链接体积与时间-DLLVM_ENABLE_PROJECTSmlirmlir只启用 MLIR 子项目-DLLVM_TARGETS_TO_BUILD空不构建任何后端目标加快编译转换器不需要 LLVM 代码生成后端-DLLVM_INCLUDE_EXAMPLESOFF/-DLLVM_INCLUDE_TESTSOFFOFF跳过示例与测试进一步缩短编译时间ninja install后MLIR 会安装到llvm-project/build/install目录下其中包含 mlir2ncnn 编译所需的lib/cmake/llvm与lib/cmake/mlir两个 CMake 包目录。3. 编译 mlir2ncnncd tools/mlir mkdir build cd build cmake .. -D LLVM_DIRpath/to/your/llvm_install/lib/cmake/llvm make这里的path/to/your/llvm_install就是上一步CMAKE_INSTALL_PREFIX指定的安装目录例如llvm-project/build/install。完成后会在当前目录生成mlir2ncnn可执行文件。注意tools/mlir有自己独立的CMakeLists.txttools/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.incTF 算子声明与定义ncnn_ops.td→ncnn_ops.h.inc/ncnn_ops.cc.incncnn 方言算子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-lookupSymbolmlir::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标准算子、TensorFlowDialectTF 算子与NCNNDialectncnn 算子解析输入parseSourceFile读取.mlir文件优化 passPassManager执行createNCNNOptimizePass()ncnn 优化 pass完成算子规范化与折叠遍历main函数对基本块中的每个 operation 统计输入输出、收集权重与 blob 名、计算引用计数输出 ncnn 文件先写出7767517魔数与[layer count] [blob count]头再依次写出权重对应的MemoryData层、Split层当某个 blob 被多处引用时以及所有模型算子层写出权重把 TF 权重的内存布局重排为 ncnn 要求的布局后写入.bin。算子映射表源码中把 TF / ncnn 方言算子映射为 ncnn 层类型主要的映射关系如下表MLIR 算子ncnn 层tf.PlaceholderInputtf.Conv2D/ncnn.KerasConv2DConvolutiontf.Conv2DBackpropInputDeconvolutiontf.DepthwiseConv2dNativeConvolutionDepthWisetf.MatMultranspose_a0 且 transpose_b1InnerProducttf.MatMul其余情况Gemmncnn.KerasDense/tf.BiasAdd折叠后的全连接InnerProductncnn.KerasBatchNormBatchNormtf.AvgPool/tf.MaxPoolPoolingtf.Meankeep_dims0 且对轴 1、2 归约Pooling全局均值池化tf.Mean其他情况Reductiontf.AddNEltwisetf.AddV2/tf.Sub/tf.Mul/tf.Maximum/tf.MinimumBinaryOptf.ConcatV2Concattf.Relu/tf.LeakyReluReLUtf.Relu6Cliptf.ReshapeReshapetf.ResizeBilinear/tf.ResizeNearestNeighborInterptf.PadPaddingtf.StridedSliceCroptf.SoftmaxSoftmaxtf.SigmoidSigmoidtf.TanhTanHtf.DepthToSpace/tf.SpaceToDepthPixelShuffle / Reorgtf.Identity/std.returnNooptf.Const不输出为层转为 MemoryData 权重对于尚未支持的算子源码会向 stderr 打印%s not supported yet!警告并把原始算子名原样写入 param按 TODO 处理因此转换前建议确认模型只使用了上表覆盖的算子集合。布局与轴序的转换NHWC → NCHWTensorFlow 默认使用 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重排并映射到ConvolutionDepthWisegroup 输入通道数全连接权重tf.MatMul的i-o布局被重排为 ncnn 的o-i布局Padding 语义TF 的SAME被映射为 ncnn 的pad -233自动计算VALID映射为pad 0EXPLICIT则按[[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 维axis3通道维会被映射为 ncnn 的axis0tf.Reshape的[n, h, w, c]被映射为 ncnn 的[w, h, c]即0w 1h 2cResize 语义tf.ResizeBilinear/tf.ResizeNearestNeighbor要求align_corners0且half_pixel_centers1否则会打印Unsupported警告。模式重写把 TF 组合算子折叠为 ncnn 算子在优化 pass 中ncnn_rewriter.td 定义了一组 TableGen 重写规则把常见的 TF 算子组合“折叠”为单个 ncnn 方言算子例如tf.Mul(x, 标量Const)→ncnn.BinaryOpop_type2乘tf.AddV2(x, 标量Const)→ncnn.BinaryOpop_type0加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.InstanceNormAffineMul(Sigmoid(x), x)→ncnn.Swish。这些规则在编译期由 TableGen 生成见 tools/mlir/CMakeLists.txt 中的ncnn_rewriterIncGen目标在运行期由NCNNOptimizePass应用是 mlir2ncnn 能把复杂 TF 计算图降级为紧凑 ncnn 图的核心机制。ncnn 方言的注册实现可进一步参阅 ncnn_dialect.cpp。常见问题与注意事项MLIR 版本不匹配mlir2ncnn 依赖特定时期的 MLIR API若编译报错优先切回文档给出的 commit74e6030bcbcc8e628f9a99a424342a0c656456f9附近版本并保证LLVM_DIR指向该版本安装的lib/cmake/llvm必须单独构建mlir2ncnn 不在 ncnn 主工程 CMake 的add_subdirectory列表中需要自行进入 tools/mlir 构建输入图要求.mlir文件必须包含名为main的函数源码通过lookupSymbolFuncOp(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),仅供参考