惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Пусть этот камень будет более крепким, чем человек

【LLM推理加速】FlashAttention 【LLM推理加速】PagedAttention 【LLM推理加速】Online Softmax LLM基础知识【1】 Transformer模型 【AI编译】LayerGroup Tiling Tile的疑惑和思考 【AI编译】深度优先的Tile调度,万事大吉? 【AI编译】多级流水线Tile调度策略 【CUDA C++】GPU内存使用【3】 【AI编译】Cache缓存地址映射 【CUDA C++】GPU存储【2】 【CUDA C++】GPU基本介绍【1】 【00】0序章-不受欢迎的来客 【转载】我来了——持续低熵 【Halide】调度优化【2】 【感想】写作进度报告5 【Halide】调度优化【1】 【转载】北大中文男足战报2 【BYOC】TVM切分子图 【转载】北大中文男足战报1 【AI编译】张量生命周期管理 SystemC 用寄存器同步建模方法 【脉动阵列】脉动阵列类型 【im2col】AScend conv accelerate 【感想】写作进度报告4 【BYOC】TVM添加自定义编译器 ccompiler 【感想】写作进度报告3 【Tengine】推理流程脑图【2】 【Tengine】推理流程脑图【1】 【编译器】使用llvm编译自定义语言【3】编译 object 【编译器】使用llvm编译自定义语言【2】转llvm IR 【编译器】使用llvm编译自定义语言【1】构建AST 【AI编译】如何进行内存分配 【感想】写作进度报告2 【AI编译】layer-group之后如何tiling 【AI编译】如何进行layer-group 【量化】连续卷积层首尾量化的可行性 【Gemm】内存对齐 【gemm】Gemm计算加速 【TVM】通过代码学习编译流程【5】FuseOps 【TVM】通过代码学习编译流程【6】CodeGen 【TVM】通过代码学习类【3.5】Pass 【TVM】通过代码学习编译流程【4】BuildRelay 【AI编译】Tiling操作能优化什么时间 【TVM】通过代码学习编译流程【3】模型编译 【TVM】通过代码学习编译流程【2】模型转换 【TVM】通过代码学习编译流程【1】必要知识 【感想】写作进度报告1 【Winograd】卷积加速算法原理及实现 SystemC 等待异步事件解决方案 【TVM】Python脚本实现模型编译和保存 【推理引擎】常见AI推理框架 【3D建模】T110E3卡迪夫蓝调皮肤模型 【TVM】C++部署运行TVM 【推理引擎】NCNN和Tengine量化推理逻辑对比 【3D建模】IS-7攻城锤流纹岩皮肤展示 【TVM】根据例子走通代码库 博客汇总目录 【Im2Col】卷积加速算法【2】NHWC 【Im2Col】卷积加速算法【1】 NCHW openBlas库的安装与简单使用 C语言工程调用Cpp库解决方案 foo Hello World
【NCNN】学习ncnn模型转换
2025-04-16 · via Пусть этот камень будет более крепким, чем человек

# 前言

本篇通过代码介绍 NCNN 的模型转换过程。模型转换过程逻辑简单,所以本篇文章只对关键节点进行介绍。NCNN 工程地址

作为初学者,错误在所难免,还望不吝赐教。

# 介绍

NCNN 是由 腾讯优图实验室 开发的 开源神经网络推理框架,专注于为 移动端和嵌入式设备 提供高效、轻量的深度学习模型部署解决方案。自 2017 年开源(基于 BSD 3-Clause 协议)以来,因其高性能、低功耗和跨平台特性,成为移动端 AI 推理的主流框架之一。

# 使用

ncnn 编译部署方法,网络上很多,也可以参考工程中的 :docs/how-to-build/how-to-build.md。编译时出现 Protoc 报错,可以参考工程中的 :docs/how-to-use-and-FAQ/FAQ-ncnn-protobuf-problem.zh.md

编译完成之后的使用命令,以 onnx 模型为例:

./build/tools/onnx/onnx2ncnn  /home/user/yourpath/model.onnx /home/user/yourpath/model_ncnn.param /home/user/yourpath/model_ncnn.bin
find yourpath/dataset/img/ -type f > imagelist.txt
./build/tools/quantize/ncnn2table /home/user/yourpath/model_ncnn.param  /home/user/yourpath/model_ncnn.bin /home/user/yourpath/dataset/imagelist.txt /home/user/yourpath/model_ncnn.table mean=[0,0,0] norm=[1,1,1] shape=[224,224,3]  thread=8 method=kl pixel=RGB
./build/tools/quantize/ncnn2int8 /home/user/yourpath/model_ncnn.param  /home/user/yourpath/model_ncnn.bin   /home/user/yourpath/model_ncnn_int8.param  /home/user/yourpath/model_ncnn_int8.bin  /home/user/yourpath/model_ncnn.table
./build/examples/simple_net /home/user/yourpath/dataset/img/img.jpg

# 模型

构建模型的代码:

from torch import nn
import torch
import numpy as np
class modeltest(nn.Module):
    def __init__(self, inp_c = 3, istrain=False):
        super(modeltest, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=inp_c, out_channels=4, kernel_size=(3, 3), groups=1, stride=(1, 1), padding=0, bias=False)
        self.bn1 = nn.BatchNorm2d(4)
        self.relu1 = nn.ReLU()
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv2 = nn.Conv2d(in_channels=4, out_channels=5, kernel_size=(3, 3), groups=1, stride=(1, 1), padding=0, bias=False)
        self.bn2 = nn.BatchNorm2d(5)
        self.relu2 = nn.ReLU()
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(5, 10)
    def forward(self, x):
        x = self.conv1(x) 
        x = self.bn1(x)  
        x = self.relu1(x)
        x = self.maxpool(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu2(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1) 
        x = self.fc(x)
        return x
if __name__ == "__main__":
    x_ = np.random.rand(1, 3, 224, 224).astype(np.float32)
    x = torch.tensor(x_)
    print(x.shape)
    model = modeltest()
    model.eval()
    print(model)
    
    torch.onnx.export(model, x, "models/simple.onnx", opset_version=12,
                      input_names=['data'], output_names=['end'])

Netron 支持多种神经网络模型的可视化,包括 onnx 和 ncnn 的 param。

以该模型为例,展示 ncnn 的模型转换过程。

onnx模型和param模型

# 模型转换

模型转换使用的是工具文件夹下的 onnx2ncnn,它的源文件位于 tools/onnx/onnx2ncnn.cpp

# 解析 onnx 的辅助工具

源文件中有大量 onnx 命名空间下的类和函数,如 onnx::ModelProtoonnx::TensorProto 等。这些类和函数链接到 build 编译文件夹下自动生成的 onnx.pb.cc 和 onnx.pb.h 源文件。

它们是怎么来的呢?

1.Protocol Buffers

Protocol Buffers 是一种由 Google 开发的语言无关、平台无关、可扩展的序列化数据格式。它允许定义数据结构,然后使用特定的编译器(protoc)生成不同编程语言的代码,用于读写这些数据结构。

2.onnx.proto 文件

onnx.proto 文件使用 Protocol Buffers 语法来定义 ONNX 模型的各种数据结构和消息类型,这些就属于元数据。它们描述了 ONNX 模型各个组成部分的结构和关系。

可以从 ONNX 官方的 GitHub 仓库(https://github.com/onnx/onnx )克隆代码,onnx.proto 文件就在 onnx 目录下。

3.onnx.pb.cc 和 onnx.pb.h 文件

有了 protoc 编译器和 onnx.proto 文件之后,就能使用 protoc --cpp_out=. onnx/onnx.proto 命令来生成 onnx.pb.cc 和 onnx.pb.h 文件

而 NCNN 是通过 CmakeLists.txt 中通过 protobuf_generate_cpp(ONNX_PROTO_SRCS ONNX_PROTO_HDRS onnx.proto) 命令,实现 onnx.pb.cc 和 onnx.pb.h 文件的生成。

4. 解析 onnx

调用 onnx.pb.cc 和 onnx.pb.h 文件中的类来解析 onnx 模型。

# 转换结果

直接看转换结果。转换过程很长,但无非是将 onnx 模型中的信息按照顺序取出来,重新构建成 ncnn 要求的样子。

model_ncnn.param(未加密文件)可以以文本的方式直接打开。该文件存储了网络结构上的所有信息。

7767517
9 9
Input            data                     0 1 data
Convolution      /conv1/Conv              1 1 data /conv1/Conv_output_0 0=4 1=3 11=3 2=1 12=1 3=1 13=1 4=0 14=0 15=0 16=0 5=1 6=108
ReLU             /relu1/Relu              1 1 /conv1/Conv_output_0 /relu1/Relu_output_0
Pooling          /maxpool/MaxPool         1 1 /relu1/Relu_output_0 /maxpool/MaxPool_output_0 0=0 1=3 11=3 2=2 12=2 3=1 13=1 14=1 15=1 5=1
Convolution      /conv2/Conv              1 1 /maxpool/MaxPool_output_0 /conv2/Conv_output_0 0=5 1=3 11=3 2=1 12=1 3=1 13=1 4=0 14=0 15=0 16=0 5=1 6=180
ReLU             /relu2/Relu              1 1 /conv2/Conv_output_0 /relu2/Relu_output_0
Pooling          /avgpool/GlobalAveragePool 1 1 /relu2/Relu_output_0 /avgpool/GlobalAveragePool_output_0 0=1 4=1
Flatten          /Flatten                 1 1 /avgpool/GlobalAveragePool_output_0 /Flatten_output_0
InnerProduct     /fc/Gemm                 1 1 /Flatten_output_0 end 0=10 1=1 2=50

第一行 “7767517” 是个用作校验的魔数。

第二行 9 9 是 9 个节点(node) + 9 个输入输出 tensor(middle tensor)

之后的 9 行是所有的节点及其拓扑信息。

例如 Input data 0 1 data ,节点类型为 Input(输入节点),名称为 data,有 0 个输入,1 个输出,输出 tensor 的名字叫做 data。
Convolution /conv1/Conv 1 1 data /conv1/Conv_output_0 0=4 1=3 11=3 2=1 12=1 3=1 13=1 4=0 14=0 15=0 16=0 5=1 6=108 这一行,节点类型是 Convolution,名称为 /conv1/Conv,有一个输入 tensor,名称为 data,有一个输出 tensor,名称为 /conv1/Conv_output_0, 后续的键值对的具体含义可以查阅 tools/onnx/onnx2ncnn.cpp 。例如键 1 和 11 都代表 kernel 的宽和高。不同类型的节点,其相同的键也可能代表不同的意思。

model_ncnn.param 文件包含了结构信息,剩下的权重偏置数据存储在 model_ncnn.bin 文件中。

# 后记

本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!