ncnn 的 MLIR 前端工具 mlir2ncnn:从源码编译到模型转换的完整指南
2026/9/20 21:28:26 网站建设 项目流程

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 方言定义,注册KerasConv2DKerasDenseKerasBatchNormBinaryOpInstanceNormSwish等算子(见 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 NinjaNinja使用 Ninja 作为生成器,加速并行构建
-DCMAKE_INSTALL_PREFIX=install相对路径MLIR 的安装前缀,后续 mlir2ncnn 编译需要引用它
-DCMAKE_BUILD_TYPE=ReleaseRelease使用优化构建,显著减少工具运行时间
-DBUILD_SHARED_LIBS=ONON构建共享库,减少链接体积与时间
-DLLVM_ENABLE_PROJECTS="mlir"mlir只启用 MLIR 子项目
-DLLVM_TARGETS_TO_BUILD=""不构建任何后端目标,加快编译(转换器不需要 LLVM 代码生成后端)
-DLLVM_INCLUDE_EXAMPLES=OFF/-DLLVM_INCLUDE_TESTS=OFFOFF跳过示例与测试,进一步缩短编译时间

ninja install后,MLIR 会安装到<llvm-project>/build/install目录下,其中包含 mlir2ncnn 编译所需的lib/cmake/llvmlib/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.tdtf_all_ops.h.inc/tf_all_ops.cc.inc(TF 算子声明与定义);
    • ncnn_ops.tdncnn_ops.h.inc/ncnn_ops.cc.inc(ncnn 方言算子);
    • ncnn_rewriter.tdncnn_rewriter.inc(自动生成的模式重写规则);
  • 链接 MLIR 的MLIRIRMLIRDialectMLIRInferTypeOpInterfaceMLIRParserMLIRPassMLIRStandardMLIRTransforms等库,并以-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.Conv2Dtf.MatMultf.Relu等标准 TF 算子,并以名为mainfunc作为入口——这一点可以从 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.paramncnn.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函数完整呈现了转换主流程:

  1. 加载方言:在MLIRContext中注册StandardOpsDialect(标准算子)、TensorFlowDialect(TF 算子)与NCNNDialect(ncnn 算子);
  2. 解析输入parseSourceFile读取.mlir文件;
  3. 优化 passPassManager执行createNCNNOptimizePass()(ncnn 优化 pass),完成算子规范化与折叠;
  4. 遍历main函数:对基本块中的每个 operation 统计输入输出、收集权重与 blob 名、计算引用计数;
  5. 输出 ncnn 文件:先写出7767517魔数与[layer count] [blob count]头,再依次写出权重对应的MemoryData层、Split层(当某个 blob 被多处引用时)以及所有模型算子层;
  6. 写出权重:把 TF 权重的内存布局重排为 ncnn 要求的布局后写入.bin

算子映射表

源码中把 TF / ncnn 方言算子映射为 ncnn 层类型,主要的映射关系如下表:

MLIR 算子ncnn 层
tf.PlaceholderInput
tf.Conv2D/ncnn.KerasConv2DConvolution
tf.Conv2DBackpropInputDeconvolution
tf.DepthwiseConv2dNativeConvolutionDepthWise
tf.MatMul(transpose_a=0 且 transpose_b=1)InnerProduct
tf.MatMul(其余情况)Gemm
ncnn.KerasDense/tf.BiasAdd折叠后的全连接InnerProduct
ncnn.KerasBatchNormBatchNorm
tf.AvgPool/tf.MaxPoolPooling
tf.Mean(keep_dims=0 且对轴 1、2 归约)Pooling(全局均值池化)
tf.Mean(其他情况)Reduction
tf.AddNEltwise
tf.AddV2/tf.Sub/tf.Mul/tf.Maximum/tf.MinimumBinaryOp
tf.ConcatV2Concat
tf.Relu/tf.LeakyReluReLU
tf.Relu6Clip
tf.ReshapeReshape
tf.ResizeBilinear/tf.ResizeNearestNeighborInterp
tf.PadPadding
tf.StridedSliceCrop
tf.SoftmaxSoftmax
tf.SigmoidSigmoid
tf.TanhTanH
tf.DepthToSpace/tf.SpaceToDepthPixelShuffle / Reorg
tf.Identity/std.returnNoop
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.MatMuli-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 维axis=3(通道维)会被映射为 ncnn 的axis=0tf.Reshape[n, h, w, c]被映射为 ncnn 的[w, h, c](即0=w 1=h 2=c);
  • Resize 语义tf.ResizeBilinear/tf.ResizeNearestNeighbor要求align_corners=0half_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,若编译报错,优先切回文档给出的 commit74e6030bcbcc8e628f9a99a424342a0c656456f9附近版本,并保证LLVM_DIR指向该版本安装的lib/cmake/llvm
  • 必须单独构建:mlir2ncnn 不在 ncnn 主工程 CMake 的add_subdirectory列表中,需要自行进入 tools/mlir 构建;
  • 输入图要求.mlir文件必须包含名为main的函数(源码通过lookupSymbol<FuncOp>("main")定位),且算子需位于支持的映射表内;
  • 输出文件格式:生成的.param7767517魔数开头,配合.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询