Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|---|---|---|
| examples | ||
| include | ||
| lib/driver_linux_a53 | ||
| src | ||
| test | ||
| .gitignore | ||
| CHANGELOG.md | ||
| Doxyfile | ||
| Kconfig | ||
| LICENSE | ||
| README.md | ||
| SConscript | ||
README.md
pnna2 神经网络推理框架简介
概述
pnna2 目录是在 PNNA 推理框架基础上形成的应用层适配版本,主要面向新一代嵌入式硬件平台的模型推理部署需求。该目录基于 pnna_lite v2.1.6 驱动版本进行开发,适配 x16 板卡及 arm64 Linux 系统环境。
pnna2 在原 PNNA 工程结构基础上进行了整体迁移与整理,主要包括驱动接口层的统一调整,使其与新版本 pnna_lite 驱动 API 保持一致,同时保留原有的推理流程结构、示例程序与测试代码组织方式,用于支撑模型在新硬件平台上的基本部署与运行验证。
该目录主要用于承载 PNNA 在新驱动版本与新硬件平台上的应用适配实现,保持与原 PNNA 工程在使用方式上的一致性,便于上层应用进行迁移与调用。
PNNA vs PNNA2 主要差异
PNNA2 是在 PNNA 基础上的新一代适配版本,主要差异如下:
- 驱动版本:PNNA 使用 pnna_lite v1.15.0;PNNA2 升级至 pnna_lite v2.1.6
- 硬件平台:PNNA 适配 x78ne 板卡;PNNA2 适配 x16 板卡
- 系统架构:PNNA 运行于 arm32 Linux;PNNA2 运行于 arm64 Linux
PNNA2 在应用层进行了封装优化,以适配新驱动接口与新硬件平台能力。
目录结构和说明
pnna2
├── examples
│ ├── README.md # 推理示例总体说明
│ └── ... # 不同模型和平台的示例工程
├── include
│ ├── nn_api.h # 应用层推理接口定义
│ ├── nn_utils.h # 通用工具函数接口
│ ├── pnna_lite.h # PNNA Lite 核心接口声明
│ ├── pnna_lite_common.h # 公共类型与宏定义
│ ├── preprocess.h # 前处理接口声明
│ └── quantize.h # 量化相关接口声明
├── src
│ ├── driver_metal_c6x # DSP 裸机环境驱动实现
│ ├── driver_rtt # RT-Thread 环境驱动实现
│ ├── nn_api.c # 推理接口实现
│ ├── nn_utils.c # 工具函数实现
│ ├── preprocess.c # 前处理流程实现
│ └── quantize.c # 量化与数据转换实现
├── lib
│ └── *.so # Linux 平台下的 PNNA 动态库文件
├── test
│ └── README.md # 测试程序说明
└── README.md # pnna 目录总览说明
目录职责说明
-
examples examples 目录提供基于 pnna 的完整推理示例工程,覆盖不同模型和运行平台。 示例主要用于展示 pnna 接口的实际使用方式,包括模型加载、输入准备、推理调用和结果处理。 示例的详细流程和注意事项不在本 README 中展开,具体说明请参考 examples/README.md 以及各示例子目录中的文档。
-
include include 目录包含 pnna 对外暴露的头文件,定义了推理相关的核心数据结构、接口函数以及通用类型。 这些头文件是应用层或示例工程使用 pnna 功能的主要入口,同时也作为 src 目录中实现代码的接口约束。
-
src src 目录实现 pnna 的核心功能逻辑,包括模型推理流程、前后处理、工具函数以及不同运行环境下的驱动适配。 driver_metal_c6x 和 driver_rtt 子目录分别用于适配 DSP 裸机和 RT-Thread 操作系统,其余源文件实现平台无关的通用推理逻辑。
-
lib lib 目录用于存放 Linux 平台下的 PNNA 动态库文件(.so)。 在 ARM Linux 环境中,PNNA 驱动的编译与 Linux 内核版本、内核配置和工具链强相关,直接提供源码在多数情况下难以复用。 因此,该目录以预编译库文件的形式提供 PNNA 驱动与底层实现,供应用程序或示例工程在 Linux 平台下直接链接使用。
-
test test 目录用于存放 PNNA Driver 的测试代码,包含单元测试和集成测试相关内容,用于验证设备管理、buffer 管理、推理流程以及前后处理等功能的正确性。 测试框架、构建方式和运行方法请参考 test/README.md。
使用说明
pnna2 目录本身不直接作为独立工程使用,而是作为推理框架库被示例工程或上层应用引用。一般使用流程为:
- 在应用或示例工程中包含 pnna2/include 中的接口头文件
- 根据目标平台选择并编译 pnna2/src 中对应的驱动与实现代码
- 通过 pnna2 提供的接口完成设备初始化、模型创建与推理执行
- 参考 examples 和 test 目录中的示例与测试代码进行功能验证
PNNA API 参考手册
本节说明适用于基于 PNNA API 开发的所有推理应用(如 ResNet, YOLOv5, YOLOv8 等)。
设备管理
在进行任何推理任务前,必须先初始化硬件设备;任务结束后需释放资源。
// 初始化 PNNA 硬件及驱动资源
int pnna_open();
// 释放硬件资源并关闭驱动
int pnna_close();
应用上下文 (App Context) 管理
PNNA 提供高层封装的 App Context,自动管理模型加载、内存分配及前后处理回调。
创建上下文
/**
* @brief 创建应用推理上下文
* @param nbg_buffer 模型二进制数据缓存
* @param preprocess 预处理回调函数(可为 NULL)
* @param postprocess 后处理回调函数(可为 NULL)
* @return app_ctx_t* 返回应用上下文指针
*/
app_ctx_t *create_app_ctx(buffer_t *nbg_buffer, \
preprocess_cb preprocess, \
postprocess_cb postprocess);
销毁上下文
// 自动释放上下文中分配的所有资源(模型句柄、Tensor Buffer等)
void destroy_app_ctx(app_ctx_t *model);
执行推理
app() 函数
app() 是执行推理的核心入口,封装了“预处理 -> 模型推理 -> 后处理”的完整链路。
int app(app_ctx_t *model, void **output, buffer_t *input, uint32_t core_index);
app() 是执行推理的高层接口,封装了:
- 参数合法性检查
- 可选预处理:
preprocess() - 模型推理: 内部调用
infer() - 可选后处理:
postprocess() - 输出组织与返回
适用于希望使用统一流程的应用程序。
app() 函数参数传递说明
app() 函数的第二个参数为 void **output,用于返回推理结果指针。
根据是否配置后处理函数,output 的实际指向对象及内存管理方式有所不同。
配置后处理函数的情况
当模型配置了后处理函数时,app() 返回的结果为最终解析后的结构化数据,其具体类型由后处理函数定义,例如目标检测模型中的 detection。
这种模式下:
- 输出数据的内存由调用者负责申请
app()仅负责将结果写入调用者提供的内存
示例如下:
detection *output = (detection *)malloc(10 * sizeof(detection));
app(model, core_index, (void **)&output, input);
注意事项:
- 必须传入
output的地址(void **),而不是直接传结构体数组 - 不要使用如下方式:
detection output[10]; // 不推荐
app(model, core_index, (void **)output, input);
上述写法会导致指针层级不匹配,可能引发未定义行为。
未配置后处理函数的情况
当未配置后处理函数时,app() 不对模型输出做解析,而是直接返回网络原始输出缓冲区。
这种模式下:
- 输出类型固定为
buffer_t * - 输出缓冲区由
app()/ 模型内部管理 - 调用者不需要也不应主动申请输出内存
示例如下:
buffer_t *output = NULL;
app(model, core_index, (void **)&output, &input_data);
注意事项:
- 初始将
output置为NULL即可 app()内部会将网络输出缓冲区指针赋值给output- 调用者只负责读取该缓冲区,不负责释放(释放规则以模型生命周期为准)
infer() 函数
更细粒度的底层推理接口
/**
* @brief 对已经量化后的buffer进行推理
*
* @param ctx [IN] 网络推理上下文
* @param output [OUT] 存放结果
* @param input [IN] 已量化后的buffer
* @return int 处理后的状态码,0表示成功,其他表示失败
*/
int infer(nn_ctx_t *ctx, uint32_t core_index, buffer_t *output, buffer_t *input);
infer() 只执行模型推理,不包含:
- 不做预处理
- 不做后处理
- 不检查输入格式
- 不管理输出内容的进一步解析
它是 app() 内部调用的核心底层接口,适用于:
- 想自行实现预处理 / 后处理
- 调试模型输出
- 性能测试
- 自定义多输入、多任务并发执行流程
底层模型接口说明
create_model() / destroy_model()
PNNA 提供底层模型生命周期管理接口,用于直接创建和销毁模型推理上下文。
nn_ctx_t *create_model(buffer_t *nbg_buffer);
void destroy_model(nn_ctx_t *ctx);
create_model() 负责:
- 加载并解析 NBG 模型
- 创建
nn_ctx_t推理上下文 - 分配模型运行所需的内部资源
- 完成与底层硬件驱动的绑定
destroy_model() 负责释放 create_model() 创建的所有模型相关资源。
该接口仅管理模型本体,不涉及预处理、后处理及输出解析。
App Context 与底层模型接口关系
PNNA 提供两套常用的推理调用方式,分别面向不同复杂度需求。
方式一:高层封装(推荐)
app_ctx_t *model = create_app_ctx(nbg_buffer, preprocess, postprocess);
app(model, core_index, (void **)&output, input);
destroy_app_ctx(model);
特点:
-
create_app_ctx()内部封装并调用create_model() -
destroy_app_ctx()内部封装并调用destroy_model() -
app()自动完成:- 参数检查
- 可选预处理
- 推理(内部调用
infer()) - 可选后处理
- 输出组织
适用于:常规应用、示例工程、希望使用统一推理流程的场景。
方式二:细粒度调用(高级用法)
nn_ctx_t *ctx = create_model(nbg_buffer);
// 用户自行完成预处理、量化等操作
infer(ctx, core_index, &output, &input);
// 用户自行完成后处理
destroy_model(ctx);
特点:
- 直接操作
nn_ctx_t - 仅调用最底层的
infer() - 不包含预处理 / 后处理 / 输出解析
适用于:自定义推理流程、性能测试、多模型协同、多任务调度等高级场景。
两种方式对比
| 项目 | App Context 方式 | 底层模型方式 |
|---|---|---|
| 创建接口 | create_app_ctx() |
create_model() |
| 推理接口 | app() |
infer() |
| 预处理 / 后处理 | 自动(可选) | 用户自行实现 |
| 使用难度 | 低 | 高 |
| 灵活性 | 中 | 高 |
| 推荐场景 | 应用层使用 | 框架级 / 高级使用 |
多输入模型支持
对于具有多个输入节点的模型(如某些特定的检测或分割网络),输入数据的构造方式如下:
// 1. 从上下文获取输入节点数量
int input_count = model->ctx->input_count;
// 2. 分配输入 buffer 数组
buffer_t *inputs = (buffer_t *)malloc(input_count * sizeof(buffer_t));
// 3. 填充每个输入节点的数据
for (int i = 0; i < input_count; ++i) {
inputs[i] = load_image(image_paths[i]); // 示例:加载图像数据
}
// 4. 执行推理
app(model, core_index, output, inputs);
资源管理 API 对照表
为了防止内存泄漏,请严格遵循以下的资源创建与销毁配对规则:
| 资源类型 | 创建/获取函数 | 销毁/释放函数 |
|---|---|---|
| 硬件设备 | pnna_open() |
pnna_close() |
| 文件缓冲 | load_binary_to_buffer(...) |
free_buffer(...) |
| 应用上下文 | create_app_ctx(...) |
destroy_app_ctx(...) |
| 图像数据 | load_image(...) / load_image_from_memory(...) |
free_image_buffer(...) |
| 参数缓冲 | create_buffer_from_qparams(...) |
destroy_buffer_array(...) |
| 底层模型 | create_model(...) |
destroy_model(...) |
标准调用流程示例
- 初始化:调用
pnna_open()。 - 加载:读取模型文件,调用
create_app_ctx()注册前后处理回调。 - 输入:加载图像并转换为
buffer_t。 - 推理:申请结果内存(如果需要),调用
app()。 - 解析:读取
output数据进行业务处理。 - 清理:依次调用
free_image_buffer,free(output),destroy_app_ctx()。 - 退出:调用
pnna_close()。
作者 {{xunyingya}}