深入解析ncnn::Net类——高效部署神经网络的核心组件
最近在学习ncnn推理框架,下面整理了ncnn::Net 的使用方法。
在移动端和嵌入式设备上进行高效的神经网络推理,要求框架具备轻量化、高性能以及灵活的扩展能力。作为腾讯开源的高性能神经网络推理框架,ncnn在这些方面表现出色。而在ncnn的核心组件中,ncnn::Net
类扮演了至关重要的角色。本文将详细介绍ncnn::Net
类的结构、功能及其使用方法,帮助开发者更好地理解和利用这一强大的工具。
目录
- Net类简介
- 构造与析构
- Option选项配置
- 自定义层注册
- 加载网络参数与模型
- 输入与输出管理
- 创建Extractor
- Vulkan支持
- 内存管理与优化
- 实例代码
- 使用示例
- 总结
1. Net类简介
ncnn::Net
类是ncnn框架中用于管理和执行神经网络的主要接口。它负责加载网络结构和权重、配置推理选项、管理层与Blob等关键资源,并提供了与外部数据交互的接口。通过Net
类,开发者可以方便地加载预训练模型、进行前向推理并获取结果,适用于多种应用场景,如图像分类、目标检测和语义分割等。
namespace ncnn {
class NCNN_EXPORT Net
{
public:
// 构造函数
Net();
// 析构函数
virtual ~Net();
public:
// 配置选项
Option opt;
// Vulkan相关设置
#if NCNN_VULKAN
void set_vulkan_device(int device_index);
void set_vulkan_device(const VulkanDevice* vkdev);
const VulkanDevice* vulkan_device() const;
#endif // NCNN_VULKAN
// 自定义层注册
int register_custom_layer(const char* type, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
virtual int custom_layer_to_index(const char* type);
int register_custom_layer(int index, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
// 加载参数与模型
int load_param_bin(const DataReader& dr);
int load_model(const DataReader& dr);
#if NCNN_STDIO
#if NCNN_STRING
int load_param(FILE* fp);
int load_param(const char* protopath);
int load_param_mem(const char* mem);
#endif // NCNN_STRING
int load_param_bin(FILE* fp);
int load_param_bin(const char* protopath);
int load_model(FILE* fp);
int load_model(const char* modelpath);
#endif // NCNN_STDIO
// 清除网络
void clear();
// 创建提取器
Extractor create_extractor() const;
// 获取输入输出信息
const std::vector<int>& input_indexes() const;
const std::vector<int>& output_indexes() const;
#if NCNN_STRING
const std::vector<const char*>& input_names() const;
const std::vector<const char*>& output_names() const;
#endif
const std::vector<Blob>& blobs() const;
const std::vector<Layer*>& layers() const;
std::vector<Blob>& mutable_blobs();
std::vector<Layer*>& mutable_layers();
protected:
// 自定义层创建
virtual Layer* create_custom_layer(int index);
virtual Layer* create_overwrite_builtin_layer(int typeindex);
private:
Net(const Net&);
Net& operator=(const Net&);
private:
NetPrivate* const d;
};
} // namespace ncnn
2. 构造与析构
Net
类提供了默认构造函数和析构函数,用于初始化和销毁网络对象。在构造函数中,网络的内部资源会被初始化,而析构函数则负责释放所有分配的资源,确保没有内存泄漏。
ncnn::Net net; // 创建Net对象
// 使用net进行操作
// Net对象在作用域结束时自动析构
3. Option选项配置
Net
类中的Option
成员用于配置网络的各种参数,如线程数、内存分配器、是否启用Vulkan计算等。这些选项可以在加载网络结构和权重之前进行设置,以优化推理性能和资源使用。
Option类概述
Option
类定义在option.h
文件中,包含了多个可配置选项,如下所示:
namespace ncnn {
class Allocator;
#if NCNN_VULKAN
class VkAllocator;
class PipelineCache;
#endif // NCNN_VULKAN
class NCNN_EXPORT Option
{
public:
Option();
public:
bool lightmode;
int num_threads;
Allocator* blob_allocator;
Allocator* workspace_allocator;
#if NCNN_VULKAN
VkAllocator* blob_vkallocator;
VkAllocator* workspace_vkallocator;
VkAllocator* staging_vkallocator;
PipelineCache* pipeline_cache;
#endif // NCNN_VULKAN
int openmp_blocktime;
bool use_winograd_convolution;
bool use_sgemm_convolution;
bool use_int8_inference;
bool use_vulkan_compute;
bool use_bf16_storage;
bool use_fp16_packed;
bool use_fp16_storage;
bool use_fp16_arithmetic;
bool use_int8_packed;
bool use_int8_storage;
bool use_int8_arithmetic;
bool use_packing_layout;
bool use_shader_pack8;
bool use_subgroup_basic;
bool use_subgroup_vote;
bool use_subgroup_ballot;
bool use_subgroup_shuffle;
bool use_image_storage;
bool use_tensor_storage;
bool use_reserved_0;
int flush_denormals;
bool use_local_pool_allocator;
bool use_shader_local_memory;
bool use_cooperative_matrix;
bool use_winograd23_convolution;
bool use_winograd43_convolution;
bool use_winograd63_convolution;
bool use_a53_a55_optimized_kernel;
bool use_reserved_7;
bool use_reserved_8;
bool use_reserved_9;
bool use_reserved_10;
bool use_reserved_11;
};
} // namespace ncnn
配置选项示例
ncnn::Net net;
ncnn::Option opt;
// 启用轻量模式
opt.lightmode = true;
// 设置线程数为4
opt.num_threads = 4;
// 启用Vulkan计算
opt.use_vulkan_compute = true;
// 使用自定义内存分配器
CustomAllocator alloc;
opt.blob_allocator = &alloc;
opt.workspace_allocator = &alloc;
// 将配置应用到网络
net.opt = opt;
4. 自定义层注册
ncnn框架允许开发者注册自定义层,以扩展框架的功能或实现特定的操作。register_custom_layer
函数用于注册自定义层,支持通过层类型名称或索引进行注册。
通过层类型名称注册
#if NCNN_STRING
int register_custom_layer(const char* type, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
virtual int custom_layer_to_index(const char* type);
#endif // NCNN_STRING
通过层索引注册
int register_custom_layer(int index, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
使用示例
假设我们有一个自定义层MyCustomLayer
,其创建和销毁函数分别为create_my_custom_layer
和destroy_my_custom_layer
。
// 定义创建函数
Layer* create_my_custom_layer();
void destroy_my_custom_layer(Layer* layer);
ncnn::Net net;
// 通过层类型名称注册
net.register_custom_layer("MyCustomLayer", create_my_custom_layer, destroy_my_custom_layer);
// 或者通过层索引注册(假设MyCustomLayer的索引为100)
net.register_custom_layer(100, create_my_custom_layer, destroy_my_custom_layer);
5. 加载网络参数与模型
Net
类提供了多种加载网络参数和模型文件的方法,支持从文件系统和内存中加载网络结构(参数)和权重(模型)。以下将详细介绍这些方法。
加载参数
参数文件定义了网络的结构,包括各层的类型、序列及其连接关系。参数文件可以是文本格式(param)或二进制格式(param.bin)。
// 加载二进制参数文件
int ret = net.load_param_bin("model.param.bin");
if (ret != 0) {
// 处理错误
}
// 加载内存中的参数数据
unsigned char* param_mem = ...; // 参数数据内存指针
int consumed = net.load_param(param_mem);
加载模型
模型文件包含了网络的权重数据。模型文件通常为二进制格式(model.bin),也可以从内存中加载。
// 加载二进制模型文件
int ret = net.load_model("model.bin");
if (ret != 0) {
// 处理错误
}
// 加载内存中的模型数据
unsigned char* model_mem = ...; // 模型数据内存指针
int consumed = net.load_model(model_mem);
从文件加载参数与模型
#if NCNN_STDIO
#if NCNN_STRING
// 从文件路径加载参数
int ret = net.load_param("model.param");
#endif // NCNN_STRING
// 从文件路径加载二进制参数
int ret = net.load_param_bin("model.param.bin");
// 从文件路径加载模型
ret = net.load_model("model.bin");
#endif // NCNN_STDIO
从Android Asset加载(仅适用于Android平台)
#if NCNN_PLATFORM_API && __ANDROID_API__ >= 9
#if NCNN_STRING
// 从Android Asset加载参数文件
net.load_param(asset);
net.load_param(asset_mgr, "path/to/model.param");
// 从Android Asset加载二进制参数文件
net.load_param_bin(asset);
net.load_param_bin(asset_mgr, "path/to/model.param.bin");
#endif // NCNN_STRING
// 从Android Asset加载模型文件
net.load_model(asset);
net.load_model(asset_mgr, "path/to/model.bin");
#endif // NCNN_PLATFORM_API
6. 输入与输出管理
ncnn::Net
类通过Blob索引和名称管理输入和输出。开发者可以通过索引或名称设置输入数据,并提取推理结果。
获取输入输出索引与名称
const std::vector<int>& input_indexes = net.input_indexes();
const std::vector<int>& output_indexes = net.output_indexes();
#if NCNN_STRING
const std::vector<const char*>& input_names = net.input_names();
const std::vector<const char*>& output_names = net.output_names();
#endif
通过索引设置输入和提取输出
ncnn::Extractor ex = net.create_extractor();
// 设置输入
ex.input(0, input_mat); // 通过Blob索引设置
// 提取输出
ncnn::Mat output_mat;
ex.extract(0, output_mat); // 通过Blob索引提取
通过名称设置输入和提取输出(需要开启NCNN_STRING)
#if NCNN_STRING
ncnn::Extractor ex = net.create_extractor();
// 设置输入
ex.input("input_blob_name", input_mat); // 通过Blob名称设置
// 提取输出
ncnn::Mat output_mat;
ex.extract("output_blob_name", output_mat); // 通过Blob名称提取
#endif
7. 创建Extractor
Extractor
类是用于执行前向推理的实例,通过Net
类创建。Extractor
支持多线程、内存分配器设置以及Vulkan加速等配置。
创建Extractor
ncnn::Extractor ex = net.create_extractor();
配置Extractor选项
ex.set_light_mode(true); // 启用轻量模式,回收中间Blob
ex.set_num_threads(4); // 设置Extractor的线程数
// 设置自定义内存分配器
CustomAllocator alloc;
ex.set_blob_allocator(&alloc);
ex.set_workspace_allocator(&alloc);
8. Vulkan支持
ncnn支持基于Vulkan的GPU加速,通过VkAllocator
和相关配置,可以在支持Vulkan的设备上利用GPU进行高效推理。
设置Vulkan设备
#if NCNN_VULKAN
// 通过设备索引设置Vulkan设备
net.set_vulkan_device(0); // 使用第一个Vulkan设备
// 或者通过VulkanDevice句柄设置
const VulkanDevice* vkdev = ...;
net.set_vulkan_device(vkdev);
#endif // NCNN_VULKAN
设置Extractor的Vulkan选项
#if NCNN_VULKAN
ncnn::Extractor ex = net.create_extractor();
ex.set_vulkan_compute(true); // 启用Vulkan计算
// 设置Vulkan内存分配器
VkAllocator vkalloc;
ex.set_blob_vkallocator(&vkalloc);
ex.set_workspace_vkallocator(&vkalloc);
ex.set_staging_vkallocator(&vkalloc);
#endif // NCNN_VULKAN
9. 内存管理与优化
ncnn通过引用计数、自定义内存分配器和内存池复用等机制,实现高效的内存管理。同时,Option
和Extractor
类提供了多种配置选项,帮助开发者根据需求优化内存使用和推理性能。
自定义内存分配器
开发者可以实现自定义的Allocator
,以满足特定的内存管理需求或优化策略。
class CustomAllocator : public ncnn::Allocator
{
public:
virtual void* fastMalloc(size_t size) override {
// 自定义内存分配逻辑
return malloc(size);
}
virtual void fastFree(void* ptr) override {
// 自定义内存释放逻辑
free(ptr);
}
};
CustomAllocator alloc;
ncnn::Option opt;
opt.blob_allocator = &alloc;
opt.workspace_allocator = &alloc;
net.opt = opt;
内存池复用与对齐
通过内存池复用和自动内存对齐,ncnn提高了内存利用率和数据访问效率,减少了内存分配和释放的开销。
class PoolAllocator : public ncnn::Allocator
{
std::vector<std::pair<size_t, void*>> freed_memory;
public:
virtual void* fastMalloc(size_t size) override {
for (auto it = freed_memory.begin(); it != freed_memory.end(); ++it) {
if (it->first >= size) {
void* ptr = it->second;
freed_memory.erase(it);
return ptr;
}
}
return malloc(size);
}
virtual void fastFree(void* ptr) override {
size_t size = /* 获取ptr对应的大小 */;
freed_memory.emplace_back(size, ptr);
}
};
PoolAllocator pool_alloc;
ncnn::Option opt;
opt.blob_allocator = &pool_alloc;
opt.workspace_allocator = &pool_alloc;
net.opt = opt;
10. 实例代码
以下是一个完整的示例,展示如何使用ncnn::Net
类加载模型、设置Option、创建Extractor并进行推理。
#include "net.h"
#include "mat.h"
#include <stdio.h>
int main() {
// 创建Net对象
ncnn::Net net;
// 配置选项
ncnn::Option opt;
opt.lightmode = true;
opt.num_threads = 4;
opt.use_winograd_convolution = true;
opt.use_sgemm_convolution = true;
opt.use_int8_inference = true;
opt.use_packing_layout = true;
net.opt = opt;
// 加载参数和模型
if (net.load_param("model.param") != 0) {
printf("Failed to load param file\n");
return -1;
}
if (net.load_model("model.bin") != 0) {
printf("Failed to load model file\n");
return -1;
}
// 创建Extractor
ncnn::Extractor ex = net.create_extractor();
ex.set_num_threads(4);
ex.set_light_mode(true);
// 准备输入数据
ncnn::Mat in = ncnn::Mat::from_pixels_resize(image_data, ncnn::Mat::PIXEL_RGB, width, height, 224, 224);
in.substract_mean_normalize(mean_vals, norm_vals);
// 设置输入
ex.input("input_blob", in);
// 提取输出
ncnn::Mat out;
ex.extract("output_blob", out);
// 处理输出
float* ptr = out;
for (int i = 0; i < out.w; i++) {
printf("Class %d: %f\n", i, ptr[i]);
}
return 0;
}
11. 使用示例
下面是一个更具体的示例,展示如何加载一个预训练的图像分类模型,并对一张图片进行推理。
#include "net.h"
#include "mat.h"
#include <stdio.h>
// 假设有一个函数读取图片并返回RGB数据
unsigned char* load_image(const char* image_path, int& width, int& height);
int main() {
// 创建Net对象
ncnn::Net net;
// 配置选项
ncnn::Option opt;
opt.lightmode = true;
opt.num_threads = 4;
opt.use_int8_inference = true;
net.opt = opt;
// 加载网络结构和模型
if (net.load_param("mobilenet_v1.param") != 0) {
printf("加载参数文件失败\n");
return -1;
}
if (net.load_model("mobilenet_v1.bin") != 0) {
printf("加载模型文件失败\n");
return -1;
}
// 创建Extractor
ncnn::Extractor ex = net.create_extractor();
// 加载并预处理图片
int width, height;
unsigned char* image_data = load_image("test.jpg", width, height);
if (!image_data) {
printf("无法加载图片\n");
return -1;
}
ncnn::Mat in = ncnn::Mat::from_pixels_resize(image_data, ncnn::Mat::PIXEL_RGB, width, height, 224, 224);
in.substract_mean_normalize(
ncnn::Mat(3, 1, (float[]) {104.f, 117.f, 123.f}), // 均值
ncnn::Mat(3, 1, (float[]) {1.f, 1.f, 1.f}) // 归一化
);
// 设置输入
ex.input("data", in);
// 提取输出
ncnn::Mat out;
ex.extract("prob", out);
// 找到概率最高的分类
float max_score = out[0];
int max_index = 0;
for (int i = 1; i < out.w; i++) {
if (out[i] > max_score) {
max_score = out[i];
max_index = i;
}
}
printf("预测结果: 类别索引 = %d, 置信度 = %f\n", max_index, max_score);
// 释放图片数据
free(image_data);
return 0;
}
12. 总结
ncnn::Net
类作为ncnn框架的核心组件,具备以下显著特点:
- 灵活的选项配置:通过
Option
类,开发者可以细粒度地配置推理参数,如线程数、内存分配策略、优化选项等。 - 高效的内存管理:支持自定义内存分配器、内存池复用和自动对齐,确保内存的高效利用和访问效率。
- 扩展性强:允许开发者注册自定义层,扩展框架功能,以适应特定的应用需求。
- 支持多种数据加载方式:支持从文件系统和内存中加载网络结构和权重,适用于不同的应用场景。
- Vulkan加速支持:通过Vulkan实现GPU加速,提升推理性能,特别适用于移动设备和嵌入式系统。
- 简便的输入输出管理:通过Blob索引和名称管理输入和输出,提供多种设置和提取接口,便于集成和使用。
- 易于使用的接口:提供简洁的API接口,结合丰富的示例代码,降低了神经网络部署的门槛。
通过本文的详细介绍,相信您已经对ncnn::Net
类有了全面的了解。无论是在资源受限的移动设备上进行高效推理,还是在嵌入式系统中实现复杂的神经网络应用,ncnn::Net
类都能为您提供强大的支持和高效的性能表现。
希望这篇博客能帮助您更好地理解和使用ncnn::Net
类,在您的深度学习项目中发挥更大的作用。如果您有任何问题或建议,欢迎在评论区留言讨论!