3516cv610 npu 开发典型功能点的介绍
8.1 Stream 管理
8.1.1 原理介绍
在SVP ACL中,Stream是一个任务队列,应用程序通过Stream来管理任务的并行,一个Stream内部的任务保序执行,即Stream根据发送过来的任务依次执行;不同Stream中的任务并行执行。一个默认Context下会挂一个默认Stream,如果不显式创建Stream,可使用默认Stream。默认tream作为接口入参时,直接传NULL。
8.1.2 单线程单 Stream
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代
码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
//显式创建一个Stream
svp_acl_rt_stream stream;
svp_acl_rt_create_stream(&stream);
uint32_t model_id = 0;
//调用触发任务的接口,例如异步模型推理,任务下发在stream1
svp_acl_mdl_dataset *input;
svp_acl_mdl_dataset *output;
svp_acl_mdl_execute_async(model_id, input, output, stream);
//调用svp_acl_rt_synchronize_stream接口,阻塞应用程序运行,直到指定Stream中的所有任务都完
成。
svp_acl_rt_synchronize_stream(stream);
//Stream使用结束后,显式销毁Stream
svp_acl_rt_destroy_stream(stream);
//......
8.1.3 单线程多 Stream
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
int32_t device_id = 0 ;
uint32_t model_id1 = 0;
uint32_t model_id2 = 1;
svp_acl_rt_context context;
svp_acl_rt_stream stream1;
svp_acl_rt_stream stream2;
//如果只创建了一个Context,线程默认将这个Context作为线程当前的Context;
//如果是多个Context,则需要调用svp_acl_rt_set_current_context接口设置当前线程的Context
svp_acl_rt_create_context(&context, device_id);
svp_acl_rt_create_stream(&stream1);
//调用触发任务的接口,例如异步模型推理,任务下发在stream1
svp_acl_mdl_dataset *input1;
svp_acl_mdl_dataset *output1;
svp_acl_mdl_execute_async(model_id1, input1, output1, stream1);
svp_acl_rt_create_stream(&stream2);
//调用触发任务的接口,例如异步模型推理, 任务下发在stream2
svp_acl_mdl_dataset *input2;
svp_acl_mdl_dataset *output2;
svp_acl_mdl_execute_async(model_id2, input1, output2, stream2);
// 流同步
svp_acl_rt_synchronize_stream(stream1);
svp_acl_rt_synchronize_stream(stream2);
//释放资源
svp_acl_rt_destroy_stream(stream1);
svp_acl_rt_destroy_stream(stream2);
svp_acl_rt_destroy_context(context);
//....
8.1.4 多线程多 Stream
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
void run_thread(svp_acl_rt_stream stream) {
int32_t device_id =0 ;
svp_acl_rt_context context;
//如果只创建了一个Context,线程默认将这个Context作为线程当前的Context;
//如果是多个Context,则需要调用svp_acl_rt_set_current_context接口设置当前线程的Context
svp_acl_rt_create_context(&context, device_id);
svp_acl_rt_create_stream(&stream);
//调用触发任务的接口
//....
//释放资源
svp_acl_rt_destroy_stream(stream);
svp_acl_rt_destroy_context(context);
}
svp_acl_rt_stream stream1;
svp_acl_rt_stream stream2;
//创建2个线程,每个线程对应一个Stream
std::thread t1(runThread, stream1);
std::thread t2(runThread, stream2);
//显式调用join函数确保结束线程
t1.join();
t2.join();
8.2 同步等待
8.2.1 原理介绍
SVP ACL提供以下几种同步机制:
● Event的同步等待:调用svp_acl_rt_synchronize_event接口,阻塞应用程序运行,等待Event完成。
● Stream内任务的同步等待:调用svp_acl_rt_synchronize_stream接口,阻塞应用程序运行,直到指定Stream中的所有任务都完成。
● Stream间任务的同步等待:调用svp_acl_rt_stream_wait_event接口,阻塞指定Stream的运行,直到指定的Event完成。支持多个Stream等待同一个Event的场景。
● Device的同步等待:调用svp_acl_rt_synchronize_device接口,阻塞应用程序运行,直到正在运算中的Device完成运算。多Device场景下,调用该接口等待的是当前Context对应的Device。
说明
Hi3516CV610不支持“Event 的同步等待”和“Stream 间任务的同步等待”。
8.2.2 关于 Event 的同步等待
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
//创建一个Event
svp_acl_rt_event event;
svp_acl_rt_create_event(&event);
//创建一个Stream
svp_acl_rt_stream stream;
svp_acl_rt_create_stream(&stream);
//stream末尾添加一个event
svp_acl_rt_record_event(event, stream);
//阻塞应用程序运行,等待event发生,也就是stream执行完成
//stream完成后产生event,唤醒执行应用程序的控制流,开始执行程序
svp_acl_rt_synchronize_event(event);
//显示销毁资源
svp_acl_rt_destroy_stream(stream);
svp_acl_rt_destroy_event(event);
//......
8.2.3 关于 Stream 内任务的同步等待
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
//显式创建一个Stream
svp_acl_rt_stream stream;
svp_acl_rt_create_stream(&stream);
uint32_t model_id = 0;
//调用触发任务的接口,例如异步模型推理,任务下发在stream1
svp_acl_mdl_dataset *input;
svp_acl_mdl_dataset *output;
svp_acl_mdl_execute_async(model_id, input, output, stream);
//调用svp_acl_rt_synchronize_stream接口,阻塞应用程序运行,直到指定Stream中的所有任务都完
成。
svp_acl_rt_synchronize_stream(stream);
//Stream使用结束后,显式销毁Stream
svp_acl_rt_destroy_stream(stream);
//......
8.2.4 关于 Stream 间任务的同步等待
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
//创建一个Event
svp_acl_rt_event event;
svp_acl_rt_create_event(&event);
//创建两个Stream
svp_acl_rt_stream s1;
svp_acl_rt_stream s2;
svp_acl_rt_create_stream(&s1);
svp_acl_rt_create_stream(&s2);
//s1末尾添加一个event
svp_acl_rt_record_event(event, s1);
//阻塞s2运行,直到指定event发生,也就是s1执行完成
//s1完成后,唤醒s2,继续执行s2的任务
svp_acl_rt_stream_wait_event(s2, event);
//阻塞应用程序运行,直到指定s2中的所有任务都完成
svp_acl_rt_synchronize_stream(s2);
//显示销毁资源
svp_acl_rt_destroy_stream(s2);
svp_acl_rt_destroy_stream(s1);
svp_acl_rt_destroy_event(event);
//......
8.2.5 关于 Device 的同步等待
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
#include "acl/svp_acl.h"
//......
//指定device
svp_acl_rt_set_device(0);
//创建context
svp_acl_rt_context ctx;
svp_acl_rt_create_context(&ctx, 0);
//创建stream
svp_acl_rt_stream stream;
svp_acl_rt_create_stream(&stream);
//阻塞应用程序运行,直到正在运算中的Device完成运算
svp_acl_rt_synchronize_device();
//资源销毁
svp_acl_rt_destroy_stream(stream);
svp_acl_rt_destroy_context(ctx);
svp_acl_rt_reset_device(0);
8.3 模型推理
8.3.1 单 单 Batch+ 固定 shape+ 单模型同步推理
1. 若涉及色域转换(转换图像格式)、图像归一化(减均值/乘系数)等,在模型加载前,需要先参见《ATC工具使用指南》转换模型。
2. 在模型推理前,需要从离线模型文件(适配SoC的离线模型)中加载模型数据到内存中,并创建svp_acl_mdl_dataset类型的数据描述模型的输出。请参见“ 模型推理资源申请”。
3. 加载模型后,再同步执行模型推理。请参见“ 模型推理”。
8.3.2 异步推理+callback 回调处理
基本原理:
异步推理场景下,关键接口的调用流程如下:
1. 调用svp_acl_init接口初始化SVP ACL。
2. 按顺序调用svp_acl_rt_set_device 、svp_acl_rt_create_context、svp_acl_rt_create_stream接口依次申请运行管理资源:Device、Context、Stream,确保可以使用这些资源执行运算、管理任务。如果不显式创建Context和Stream,您可以使用svp_acl_rt_set_device接口隐式创建的默认Context和默认Stream,但默认Context和默认Stream存在如下限制:
– 一个Device对应一个默认Context,默认Context不能通过svp_acl_rt_destroy_context接口来释放。
– 一个Device对应一个默认Stream,默认Stream不能通过svp_acl_rt_destroy_stream接口来释放。默认Stream作为接口入参时,直接传NULL。
3. 申请模型推理资源。
a. 加载模型。
svp_acl_mdl_load_from_mem:从内存加载离线模型数据。
b. 调用svp_acl_mdl_get_desc接口获取成功加载的模型的描述信息。
c. 初始化内存,用于存放模型推理的输入数据、输出数据。
此处需要用户可自行定义函数实现以下关键点:
i. 调用svp_acl_mdl_create_dataset接口创建svp_acl_mdl_dataset类型的数据(描述模型的输入/输出)。
ii. 模型可能存在多个输入/输出,调用svp_acl_create_data_buffer接口创建svp_acl_data_buffer类型的数据(描述每个输入/输出数据的内存地址、内存大小)。
模型输入数据的内存大小,根据实际读入的图片数据的大小来确定。
模型输出数据的内存大小,可调用svp_acl_mdl_get_output_size_by_index接口根据3.b中模型描述信息获取每个输出需占用的内存大小。
iii. 调用svp_acl_mdl_add_dataset_buffer接口向svp_acl_mdl_dataset中增加svp_acl_data_buffer
4. 执行模型异步推理和callback(用于处理模型推理的结果)。
此处需要用户可自行定义函数实现以下关键点:
a. 创建新线程(例如t1),在线程函数内调用svp_acl_rt_process_report接口,等待指定时间后,触发回调函数(例如CallBackFunc,用于处理模型推理结果)。
b. 调用svp_acl_rt_subscribe_report接口,指定处理Stream上回调函数(CallBackFunc)的线程(t1),回调函数需要提前创建,用于处理模型推理的结果(例如,将推理结果写入文件、从推理结果中获取topn置信度的类别标识等)。
c. 调用svp_acl_mdl_execute_async接口执行异步模型推理。
d. 调用svp_acl_rt_launch_callback接口,在Stream的任务队列中增加一个需要在板端环境上执行的回调函数(CallBackFunc)。
e. 调用svp_acl_rt_synchronize_stream接口,阻塞应用程序运行,直到指定Stream中的所有任务都完成。
f. 调用svp_acl_rt_unsubscribe_report接口,取消线程注册,Stream上的回调函数(CallBackFunc)不再由指定线程(t1)处理。
g. 模型推理结束后,调用svp_acl_mdl_unload接口卸载模型。
5. 所有数据处理结束后,按顺序调用svp_acl_rt_destroy_stream、svp_acl_rt_destroy_context、svp_acl_rt_reset_device接口依次释放运行管理资源,包括Stream、Context、Device。
6. 调用svp_acl_finalize接口实现SVP ACL去初始化。
示例代码:
您可以从acl_resnet50_async样例中查看完整样例代码,以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
在acl_resnet50_async样例中:
– 运行可执行文件,不带参数时:
▪ 执行模型异步推理的次数默认为10次,对应代码中的g_executeTimes变量;
▪ callback间隔默认为1,表示1次异步推理后,下发一次callback任务,对应代码中的g_callbackInterval变量;
▪ 内存池中的内存块的个数默认为10个,对应代码中的g_memoryPoolSize。
– 运行可执行文件,带参数时:
▪ 第一个参数表示执行模型异步推理的次数;
▪ 第二个参数表示下发callback间隔,参数值为0时表示不下发callback任务,参数值为非0值(例如m)时表示m次异步推理后下发一次callback任务;
▪ 第三个参数表示内存池中内存块的个数,内存块个数需大于等于模型异步推理的次数。用户可根据输入图片数量,来调整内存块的个数,例如有2张输入图片、内存块个数为2时,则1张图片1个内存块;例如有3张输入图片、内存块个数为10时,则执行10次循环,每(10/3取整)个内存块对应同一张图片,剩下1个内存块随机对应1张图片。内存块中存放是模型推理的输入数据、输出数据,若多个内存块对应的是同一张图片,则多个内存块中存放的是相同的输入数据、输出数据,用于输入图片少但又想模拟大量图片数据的场景。
#include "acl/svp_acl.h"
//......
//1. SVP ACL初始化
//此处的..表示相对路径,相对可执行文件所在的目录
//例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录
const char *aclConfigPath = "../src/acl.json";
svp_acl_error ret = svp_acl_init(aclConfigPath);
//2. 申请运行管理资源
extern bool g_isDevice;
ret = svp_acl_rt_set_device(deviceId_);
ret = svp_acl_rt_create_context(&context_, deviceId_);
ret = svp_acl_rt_create_stream(&stream_);
//获取当前svp_acl软件栈的运行模式,根据不同的运行模式,后续的内存申请、内存复制
等接口调用方式不同
svp_acl_rt_run_mode run_mode;
ret = svp_acl_rt_get_run_mode(&runMode);
g_isDevice = (runMode == ACL_DEVICE);
//3. 申请模型推理资源
//此处的..表示相对路径,相对可执行文件所在的目录
//例如,编译出来的可执行文件存放在out目录下,此处的..就表示out目录的上一级目录
const char* omModelPath = "../model/resnet50.om"
//3.1 加载模型
//根据模型文件获取模型执行时所需的权值内存大小、工作内存大小,并申请权值内存、
工作内存
ret = svp_acl_rt_malloc(&modelMemPtr_, modelMemSize_,
SVP_ACL_MEM_MALLOC_NORMAL_ONLY);
//加载离线模型文件,模型加载成功,返回标识模型的ID。
ret = svp_acl_mdl_load_from_mem(modelPath, &modelId_, modelMemPtr_,
modelMemSize_);
//3.2 根据模型的ID,获取该模型的描述信息
modelDesc_ = svp_acl_mdl_create_desc();
ret = svp_acl_mdl_get_desc(modelDesc_, modelId_);
//3.3 自定义函数InitMemPool,初始化内存池,存放模型推理的输入数据、输出数据
//-----自定义函数InitMemPool内部的关键实现-----
string testFile[] = {
"../data/dog1_1024_683.bin",
"../data/dog2_1024_683.bin"
};
size_t fileNum = sizeof(testFile) / sizeof(testFile[0]);
for (size_t i = 0; i < g_memoryPoolSize; ++i) {size_t index = i % (sizeof(testFile) / sizeof(testFile[0]));// model processuint32_t devBufferSize;//自定义函数GetDeviceBufferOfFile,完成以下功能://获取Host上存放输入图片数据的内存及内存大小、将图片数据从Host传输到Device、获取Device上存放输入图片数据的内存及内存大小void *picDevBuffer = Utils::GetDeviceBufferOfFile(testFile[index], devBufferSize);aclmdlDataset *input = nullptr;//自定义函数CreateInput,创建aclmdlDataset类型的数据input,用于存放模型推理的输入数据Result ret = CreateInput(picDevBuffer, devBufferSize, input);svp_acl_mdl_dataset *output = nullptr;//自定义函数CreateOutput,创建svp_acl_mdl_dataset类型的数据output,用于存放模型推理的输出数据,modelDesc表示模型的描述信息CreateOutput(output, modelDesc);{std::lock_guard<std::recursive_mutex> lk(freePoolMutex_);freeMemoryPool_[input] = output;}
}
//-----自定义函数InitMemPool内部的关键实现-----
//4 模型推理
//4.1 创建线程tid,并将该tid线程指定为处理Stream上回调函数的线程
//其中ProcessCallback为线程函数,在该函数内调用svp_acl_rt_process_report接口,等待指定时间后,触发回调函数处理
pthread_t tid;
(void)pthread_create(&tid, nullptr, ProcessCallback, &s_isExit);
//4.2 指定处理Stream上回调函数的线程
svp_acl_error aclRt = svp_acl_rt_subscribe_report(tid, stream_);
//4.2 创建回调函数,用户处理模型推理的结果,由用户自行定义
void ModelProcess::CallBackFunc(void *arg)
{std::map<svp_acl_mdl_dataset *, svp_acl_mdl_dataset *> *dataMap =(std::map<svp_acl_mdl_dataset *, svp_acl_mdl_dataset *> *)arg;svp_acl_mdl_dataset *input = nullptr;svp_acl_mdl_dataset *output = nullptr;MemoryPool *memPool = MemoryPool::Instance();for (auto& data : *dataMap) {ModelProcess::OutputModelResult(data.second);memPool->FreeMemory(data.first, data.second);}delete dataMap;
}
//4.3 自定义函数ExecuteAsync,执行模型推理
//-----自定义函数ExecuteAsync内部的关键实现-----
bool isCallback = (g_callbackInterval != 0);
size_t callbackCnt = 0;
std::map<svp_acl_mdl_dataset *, svp_acl_mdl_dataset *> *dataMap = nullptr;svp_acl_mdl_dataset *input = nullptr;
svp_acl_mdl_dataset *output = nullptr;
MemoryPool *memPool = MemoryPool::Instance();
for (uint32_t cnt = 0; cnt < g_executeTimes; ++cnt) {if (memPool->mallocMemory(input, output) != SUCCESS) {ERROR_LOG("get free memory failed");return FAILED;}
//执行异步推理svp_acl_error ret = svp_acl_mdl_execute_async(modelId_, input, output,stream_);if (isCallback) {if (dataMap == nullptr) {dataMap = new std::map<svp_acl_mdl_dataset *, svp_acl_mdl_dataset *>;if (dataMap == nullptr) {ERROR_LOG("malloc list failed, modelId is %u", modelId_);memPool->FreeMemory(input, output);return FAILED;}}(*dataMap)[input] = output;callbackCnt++;if ((callbackCnt % g_callbackInterval) == 0) {//在Stream的任务队列中增加一个需要在Host上执行的回调函数ret = svp_acl_rt_launch_callback(CallBackFunc, (void *)dataMap,SVP_ACL_CALLBACK_BLOCK, stream_);if (ret != ACL_SUCCESS) {ERROR_LOG("launch callback failed, index=%zu", callbackCnt);memPool->FreeMemory(input, output);delete dataMap;return FAILED;}dataMap = nullptr;}
}
}
//-----自定义函数ExecuteAsync内部的关键实现-----
//4.4 对于异步推理,需阻塞应用程序运行,直到指定Stream中的所有任务都完成
svp_acl_rt_synchronize_stream(stream_);
//4.5 取消线程注册,Stream上的回调函数不再由指定线程处理
aclRt = svp_acl_rt_unsubscribe_report(static_cast<uint64_t>(tid), stream_);
s_isExit = true;
(void)pthread_join(tid, nullptr);
//5 释放运行管理资源
svp_acl_error ret = svp_acl_rt_destroy_stream(stream_);
ret = svp_acl_rt_destroy_context(context_);
ret = svp_acl_rt_reset_device(deviceId_);
//6 SVP ACL去初始化
ret = svp_acl_finalize();
//......
8.3.3 多模型推理
多模型推理的基本流程与单模型类似,请参见 单Batch+ 固定shape+ 单模型同步推理。
多模型推理与单模型推理的不同点如下:
● 关于模型加载,如果涉及多个模型,需调用多次模型加载接口。svp_acl_mdl_load_from_mem:从内存加载离线模型数据。
● 关于模型推理,如果涉及多个模型,需调用多次模型推理接口。调用svp_acl_mdl_execute接口实现同步模型推理。
8.3.4 动态 AI PP
基本原理:
若模型推理时包含动态AIPP特性,在模型推理时,需调用SVP ACL提供的接口设置模型推理时需使用的AIPP配置,完整流程请参见 模型执行。关键原理说明如下:
1. 加载模型。模型加载的详细流程,请参见 模型加载,模型加载成功后,返回标识模型的ID。
对于动态AIPP,模型支持的AIPP模式已提前在构建模型时配置,在 模型加载时,会新增一个输入(下文简称动态AIPP输入),在模型推理时通过该新增的输入提供具体的AIPP参数值。
例如,a输入的AIPP是动态的,在模型加载后,会有与a对应的b输入来描述a的AIPP配置信息。在模型执行时,准备a输入的数据结构请参见 准备模型执行的输入/ 输出数据,准备b输入的数据结构、设置b输入的数据请依次参见2、3。
2. 创建svp_acl_mdl_dataset类型的数据,用于描述模型执行的输入、输出,详细调用流程请参见 准备模型执行的输入/ 输出数据。
其中,动态AIPP输入的注意点如下:
a. 申请动态AIPP输入对应的内存前,需要先需调用svp_acl_mdl_get_input_aipp_type接口查询指定模型的指定输入是否有关联的动态AIPP输入,若有,则输出标识动态AIPP输入的index,该参数值可作为svp_acl_mdl_set_aipp_by_input_index接口的入参之一,来设置对应输入上的动态AIPP参数值;为避免在错误的输入上设置动态AIPP参数,用户可调用acl_mdl_get_input_name_by_index接口先获取指定输入index的输入名称,根据输入名称所对应的index设置动态AIPP参数。
b. 调用svp_acl_mdl_get_input_size_by_index根据index获取输入内存大小。
c. 调用svp_acl_rt_malloc接口根据b中的大小申请内存。申请动态AIPP输入对应的内存后,无需用户设置该内存中的数据(否则可能会导致业务异常),用户调用d中的接口后,系统会自动向该内存中填入数据。
d. 调用svp_acl_create_data_buffer接口创建svp_acl_data_buffer类型的数据,用于存放动态AIPP输入数据的内存地址、内存大小。
e. 调用svp_acl_mdl_create_dataset接口创建svp_acl_mdl_dataset类型的数据,并调用svp_acl_mdl_add_dataset_buffer接口向svp_acl_mdl_dataset类型的数据中增加svp_acl_data_buffer类型的数据。
3. 在成功加载模型之后,执行模型之前,设置动态AIPP参数。
a. 根据输入名称(固定为SVP_ACL_DYNAMIC_AIPP_NAME),获取模型中标识动态AIPP输入的index。
b. 调用svp_acl_mdl_create_aipp接口创建svp_acl_mdl_aipp类型。
c. 根据实际需求,调用svp_acl_mdl_aipp数据类型下的操作接口设置动态AIPP参数值。动态AIPP场景下,svp_acl_mdl_set_aipp_input_format接口、svp_acl_mdl_set_aipp_src_image_size接口(设置原始图片的宽和高)必须调用。
d. 调用svp_acl_mdl_set_input_aipp接口设置模型推理时的动态AIPP数据。带有动态AIPP的模型输入必须配置动态AIPP参数。
e. 及时调用svp_acl_mdl_destroy_aipp接口销毁svp_acl_mdl_aipp类型。
4. 执行模型。
例如,调用svp_acl_mdl_execute接口(同步接口)执行模型。
须知
● 模型中需要进行输入动态AIPP处理的data节点,svp_acl_mdl_get_input_size_by_index接口获取的内存大小是按照图像输入最大值来计算所得;设置动态AIPP输入的参数后,获取内存大小是按设置值计算所得。
● 动态AIPP和动态Batch同时使用时:
1. 动态AIPP只支持Batch为1的配置,不支持对多Batch的输入/输出分别配置动态AIPP参数。
2. 配置的动态AIPP参数同时作用于一次多Batch推理的所有输入/输出图片。
● 动态AIPP和动态分辨率同时使用时:
1. 若在设置动态AIPP参数时,需确保通过AIPP转换之后的宽、高与通过svp_acl_mdl_set_dynamic_hw_size接口设置的宽、高相等。
2. 模型中需要进行动态AIPP处理的data节点,其对应的最大输入宽高不能小于动态分辨率最大分辨率(宽、高)。
● 输出的动态AIPP流程和动态AIPP流程类似,输出动态AIPP只支持裁剪功能。
示例代码
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
//1.模型加载,加载成功后,再设置动态AIPP参数值
//......
//2.准备模型描述信息svp_acl_model_desc,准备模型的输入数据input和模型的输出数据output
//3.自定义函数,设置动态AIPP参数值
int model_set_dynamic_aipp()
{
//3.1 获取标识动态AIPP输入的index
size_t index;
svp_acl_error ret;
size_t input_num;
svp_acl_mdl_aipp *aipp_set = svp_acl_mdl_create_aipp(1);
//svp_acl_model_desc为svp_acl_mdl_create_desc表示模型描述信息,根据1中加载成功的模型的ID,获取该
模型的描述信息
input_num = svp_acl_mdl_get_num_inputs(svp_acl_model_desc);
for (index = 0; index < input_num; index++) {
//获取输入的AIPP类型,model_id为模型ID,aipp_type为返回的AIPP类型,attached_index为关联的AIPP
输入
ret = svp_acl_mdl_get_input_aipp_type(model_id, index, &aipp_type, &attached_index);
if (aipp_type != SVP_ACL_DATA_WITH_DYNAMIC_AIPP) continue;
//设置动态AIPP参数值
ret = svp_acl_mdl_set_aipp_src_image_size(aipp_set, 256, 224);
ret = svp_acl_mdl_set_aipp_input_format(aipp_set, SVP_ACL_YUV420SP_U8);
ret = svp_acl_mdl_set_aipp_csc_params(aipp_set, 1, 1192, 2166, 0, 1192, -218, -547, 1192, 0, 1836, 0,
0, 0, 16, 128, 128);
ret = svp_acl_mdl_set_aipp_rbuv_swap_switch(aipp_set, 0);
ret = svp_acl_mdl_set_aipp_dtc_pixel_mean(aipp_set, 0, 0, 0, 0, 0);
ret = svp_acl_mdl_set_aipp_dtc_pixel_min(aipp_set, 0, 0, 0, 0, 0);
ret = svp_acl_mdl_set_aipp_pixel_var_reci(aipp_set, 1.0, 1.0, 1.0, 1.0, 0);
ret = svp_acl_mdl_set_aipp_crop_params(aipp_set, 1, 2, 2, 224, 224, 0);
//设置对应输入的动态AIPP参数,也可以用svp_acl_mdl_set_aipp_by_input_index(model_id, input, index,
aipp_set)接口设置
ret = svp_acl_mdl_set_input_aipp(model_id, input, attached_index, aipp_set);
}
ret = svp_acl_mdl_destroy_aipp(aipp_set);
//......
}
//4.自定义函数,执行模型
int model_execute(int index)
{
svp_acl_error ret;
//4.1 调用自定义函数,设置动态AIPP参数值
ret = model_set_dynamic_aipp();
//4.2 执行模型,model_id表示加载成功的模型的ID,input和output分别表示模型的输入和输出
ret = svp_acl_mdl_execute(model_id, input, output);
//......
}
//5.处理模型推理结果
//TODO
8.3.5 WorkBuf 内存复用
基本原理
在模型推理时,输出临时数据到WorkBuf时,可能输入内存已加载完成或者输出内存还没有使用,这个时候如果复用这部分输入或者输出内存,则可以减少WorkBuf,有利于内存小型化。关键步骤说明如下:
步骤1 加载模型。模型加载的详细流程,请参见 模型加载,模型加载成功后,返回标识模型的ID。
步骤2 创建svp_acl_mdl_dataset类型的数据,用于描述模型执行的输入、输出,详细调用流程请参见 准备模型执行的输入/ 输出数据。
其中,WorkBuf内存复用的注意点如下:
1. 通过接口svp_acl_mdl_get_reuse_buffer_type获取模型中WorkBuf与输入输出内存Buf复用的类型svp_acl_buffer_reuse_type。
2. 在申请输入输出内存时,需要根据复用类型svp_acl_buffer_reuse_type来按要求申请内存。比如说获取到的复用类型SVP_ACL_BUFFER_REUSE_INOUT_CONT,则需要所有非扩展的输入输出内存连续,这时可以按所有非扩展的输入输出内存大小之和来申请一大块内存,再通过各个输入输出大小来偏移来获取内存块,然后填充到输入输出内存集中。
3. 可以通过接口svp_acl_mdl_get_input_reuse_type或者svp_acl_mdl_get_output_reuse_type获取当前输入输出内存的共享类型。
步骤3 执行模型。
例如,调用svp_acl_mdl_execute接口(同步接口)执行模型。
步骤4 释放资源,卸载模型。
释放模型输入/输出信息、内存等资源,卸载模型。如果步骤2.2一次申请一大块连续内存,则释放时也只用一次释放。
须知
● 模型在离线生成的时候指定WorkBuf和输入输出内存的复用类型,具体操作请参考《ATC工具使用指南》。
● 当输入内存与WorkBuf复用时,模型执行过程中会破坏输入内存源数据,如果需要保证输入源数据的完整性,则模型离线生成过程中需要关闭输入内存复用选项。
● 支持动态AIPP的输入输出不支持与workBuf的内存复用。
● 数据安全场景下,不支持WorkBuf的内存复用。
示例代码
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
//1.申请运行管理资源,包括设置用于计算的Device、创建Context、创建Stream
//......
//2.模型加载,加载成功后,返回标识模型的model_id
//......
//3.创建svp_acl_mdl_dataset类型的数据,用于描述模型的输入数据input_dataset、输出数据output_dataset
void *virt_addr = null;
size_t input_size[], ouput_size[]; /* 存放各个输入输出所需内存大小 */
size_t input_stride[], output_stride[]; /* 存放各个输入输出的stride大小 */
//3.1 根据模型描述符model_desc获取WorkBuf复用类型
svp_acl_buffer_reuse_type reuse_type = svp_acl_mdl_get_reuse_buffer_type(model_desc);
//3.2 根据复用类型申请内存,仅提供非扩展输入内存和输出内存都需要连续的样例
// input_num和output_num为非扩展输入输出的个数(可通过svp_acl_mdl_get_input_aipp_type和
svp_acl_mdl_get_output_aipp_type排除扩展的动态AIPP输入和输出)
static void svp_npu_reuse_inout_continue(void)
{svp_acl_data_buffer* data_buf = HI_NULL;size_t size = 0;int i;for (i = 0; i < input_num; i++) {size += input_size[i];}for (i = 0; i < output_num; i++) {size += output_size[i];}/* 申请size大小的内存 */svp_acl_rt_malloc_cached(&virt_addr, size, SVP_ACL_MEM_MALLOC_NORMAL_ONLY);for (i = 0; i < input_num; i++) {data_buf = svp_acl_create_data_buffer(virt_addr, input_size[i], input_stride[i]);svp_acl_mdl_add_dataset_buffer(input_dataset, data_buf);virt_addr += input_size[i];}for (i = 0; i < output_num; i++) {data_buf = svp_acl_create_data_buffer(virt_addr, output_size[i], out_stride[i]);svp_acl_mdl_add_dataset_buffer(output_dataset, data_buf);virt_addr += output_size[i];}// 申请扩展的输入输出并加入dataset数据集,包括task_buf、work_buf以及可能存在的动态AIPP的输入和输
出
// ...
}
if (reuse_type == SVP_ACL_BUFFER_REUSE_IN_CONT) {
// 非扩展输入内存需要连续
} else if (reuse_type == SVP_ACL_BUFFER_REUSE_OUT_CONT) {
// 非扩展输出内存需要连续
} else if (reuse_type == SVP_ACL_BUFFER_REUSE_IN_OUT_CONT) {
// 非扩展输入内存需要连续,非扩展输出内存需要连续
} else if (reuse_type == SVP_ACL_BUFFER_REUSE_INOUT_CONT) {
// 非扩展输入内存和输出内存都需要连续
svp_npu_reuse_inout_continue();
} else if (reuse_type == SVP_ACL_BUFFER_REUSE_INOUTWORK_CONT) {
// 非扩展输入内存、输出内存和workbuf都需要连续
} else {
// 正常申请,不需要强调各输入输出内存的连续性
}
//4.填充输入并刷Cache
//TODO
//5.执行模型
ret = svp_acl_mdl_execute(model_id, input, output);
//6.输出内存刷Cache,处理模型推理结果
//TODO
//7.释放内存
svp_acl_rt_free(virt_addr);
//8.释放描述模型输入/输出信息、内存等资源,卸载模型
//......
//9.释放运行管理资源
//......
8.4 带 cache 属性的内存管理
基本原理:
通过svp_acl_rt_malloc_cached接口申请的内存支持cache缓存,对于频繁使用的内存,建议使用本接口分配内存,这样可以提高cpu的读写效率,提升系统性能,但需要用户处理cpu与硬件设备(如 图像分析引擎 )之间的数据一致性问题,SVP ACL提供了svp_acl_rt_mem_flush接口将cache中的数据刷新到ddr中,从而确保硬件设备(如 图像分析引擎 )可以从ddr中获取最新数据进行处理,SVP ACL还提供了svp_acl_rt_mem_invalidate接口将cache中的数据置为无效(如果cache被CPU写操作,调用该接口功能会将数据刷到ddr),从而确保cpu可以获取到ddr中的经 图像分析引擎 处理后的结果数据。
如果任务(例如,模型加载)不涉及cpu与硬件设备(如 图像分析引擎 )之间的数据交互,则不涉及两者之间的数据一致性问题,因此就不需要调用svp_acl_rt_mem_flush接口和svp_acl_rt_mem_invalidate接口。
示例代码:
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
示例中,运行管理资源申请与释放请参见“ 运行管理资源申请”和“ 运行管理资源释放”,模型加载的接口调用流程请参见“ 模型加载”,模型推理的接口调用流程、准备模型推理的输入/输出数据的接口调用流程请参见“ 模型执行”。
//1.申请运行管理资源,包括设置用于计算的Device、创建Context、创建Stream
//......
//2.模型加载,加载成功后,返回标识模型的model_id
//......
//3.创建svp_acl_mdl_dataset类型的数据,用于描述模型的输入数据input、输出数据output
void *input_dev_buffer = null;
void *output_dev_buffer = null;
size_t input_size;
size_t ouput_size;
size_t input_stride, output_stride;
//3.1 申请支持cache缓存的内存:input_dev_buffer用于存放模型推理的输入数据
svp_acl_error ret = svp_acl_rt_malloc_cached(&input_dev_buffer, input_size,
SVP_ACL_MEM_MALLOC_NORMAL_ONLY);
svp_acl_mdl_dataset *input = svp_acl_mdl_create_dataset();
svp_acl_data_buffer* input_data = svp_acl_create_data_buffer(input_dev_buffer, input_size, input_stride);
ret = svp_acl_mdl_add_dataset_buffer(input, input_data);
//将输入数据读入内存中,该自定义函数ReadFile由用户实现
read_file(file_name, input_dev_buffer, input_size);
//3.2 申请支持cache缓存的内存:output_dev_buffer用于存放模型推理的输出数据
ret = svp_acl_rt_malloc_cached(&output_dev_buffer, ouput_size,
SVP_ACL_MEM_MALLOC_NORMAL_ONLY);
svp_acl_mdl_dataset *output = svp_acl_mdl_create_dataset();
svp_acl_data_buffer* output_data = svp_acl_create_data_buffer(output_dev_buffer, output_size,
output_stride);
ret = svp_acl_mdl_add_dataset_buffer(output, output_data);
//4.把对应cache中的数据刷新到ddr
//示例以获取第一个输入为例,如果有多个输入,可调用svp_acl_mdl_get_dataset_num_buffers接口获取输入个
数
svp_acl_data_buffer *input_data_buffer = svp_acl_mdl_get_dataset_buffer(input, 0);
void *input_data_buffer_addr = svp_acl_get_data_buffer_addr(input_data_buffer);
size_t input_data_buffer_size = svp_acl_get_data_buffer_size(input_data_buffer);
ret = svp_acl_rt_mem_flush(input_data_buffer_addr, input_data_buffer_size);
//5.执行模型
ret = svp_acl_mdl_execute(model_id, input, output);
//6.执行svp_acl_rt_mem_invalidate接口,把对应cache中的数据置为无效,确保cpu获取到的是ddr中的数据
//示例以获取第一个输出为例,如果有多个输出,可调用svp_acl_mdl_get_dataset_num_buffers接口获取输出个
数
svp_acl_data_buffer *out_data_buffer = svp_acl_mdl_get_dataset_buffer(output, 0);
void *output_data_buffer_addr = svp_acl_get_data_buffer_addr(out_data_buffer);
size_t output_data_buffer_size = svp_acl_get_data_buffer_size(out_data_buffer);
ret = svp_acl_rt_mem_invalidate(output_data_buffer_addr, output_data_buffer_size);
//7.处理模型推理结果
//TODO
//8.释放内存
svp_acl_rt_free(input_dev_buffer);
svp_acl_rt_free(output_dev_buffer);
//9.释放描述模型输入/输出信息、内存等资源,卸载模型
//......
//10.释放运行管理资源
//......
8.5 推理使用模式识别 CPU 算子
基本原理:
SoC推理场景下,如果模型中有 模式识别 Core不支持的算子,需要通过 模式识别 CPU算子来实现,这时,异步推理场景下可以调用svp_acl_ext_process_aicpu_task接口来接收 模式识别 CPU算子并在CPU上执行,同步推理场景下无需用户做特殊处理。下面就异步推理关于该接口的使用方式进行进一步介绍。
模式识别 CPU算子在CPU侧执行时可能会引入一些问题,因此用户需要关注以下几点:
1. 为了不影响整个推理业务,需要创建 模式识别 CPU工作线程对 模式识别 CPU算子进行处理,确保 模式识别 CPU算子处理和推理业务同时在CPU侧运行,且可保证各自的执行效率。
2. 需要用户在 模式识别 CPU工作线程内,调用svp_acl_ext_process_aicpu_task接口来接收 模式识别 CPU算子并执行。
3. 用户场景差异比较大,需要根据自己业务需求来部署 模式识别 CPU工作线程,例如线程数量,是否绑核。
示例代码:
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
示例中,异步推理流程请参见“ 异步推理+callback 回调处理 ”。
//1.实现 模式识别 CPU工作线程处理函数
static bool thread_exit_flag = false;
void *cpu_func_async_test(void *param)
{
while (thread_exit_flag == false) {
svp_acl_ext_process_aicpu_task(1000);
}
return NULL;
}
//2.主线程创建 模式识别 CPU工作线程
static pthread_t g_tid;
pthread_create(&g_tid, NULL, cpu_func_async_test, NULL);
//3.异步推理流程
//......
//4.释放线程
thread_exit_flag = true;
pthread_join(g_tid, NULL);
须知
● 编译代码的时候,需要匹配对应的头文件svp_acl_ext.h。
● 含有模式识别CPU算子的模型在离线生成时,可配置在CPU处理时AICore是否处于忙等。如果配置为忙等,则AICore不会释放,等到CPU处理完成后继续AICore任务;如果不是忙等,则NPU会在当前模型处理CPU任务时切换到其它Stream上处理其它模型的AICore任务。具体配置可参考《ATC工具开发指南》的“功能配置选项”章节。
● 含有模式识别CPU算子并且配置为非忙等的模型在执行时,有一些上下文信息保存在模型的work buf中,所以不建议此模型的work buf和其它不在同一个流上的模型之间共享,否则可能会执行错误。
8.6 任务优先级
优先级调度:硬件是基于任务的优先级来实现调度的。调度特点:
● 任务优先级分为0~7级,数值越低,优先级越高,0的优先级最高。
● 不同优先级任务的调度策略发生在不同的Stream之间,以Stream当前运行任务的优先级为准。
● 高优先级任务先于低优先级任务进行调度。
● 同等优先级情况下,等待时间长的任务优先调度。
● 同等优先级同等等待时长下,任务所在任务流的SQ编号越小的优先调度(SQ编号可通过Proc 信息说明中的“sq_id”字段获取)。
须知
● 由于硬件记录等待时长的时间精度为10.6ms,所以会出现两个下发时间差不超过10.6ms的任务被认定为等待相同时长的情况。
● 由于硬件记录的最大等待时长为339.2ms,所以会出现两个等待时间超过339.2ms的任务被认定为等待相同时长的情况。
优先级抢占:优先级抢占是为了保障高优先级的任务能实时优先运行。优先级抢占的特点如下:
● 优先级抢占发生在不同的Stream之间,高优先级的任务可以打断正在运行的低优先级任务。
● 低优先级任务被打断的时间边界是模型融合层执行完成,被打断后会保存现场并释放执行权。
● 被打断的任务会在高优先级任务都完成后重新被调度。
须知
● 优先级抢占场景下,高优先级任务需要开启优先级抢占使能,可参考svp_acl_mdl_config_attr中SVP_ACL_MDL_PRIORITY_PREEMP_EN_BOOL的配置说明;
● 优先级抢占场景下,被抢占的低优先级模型需要在模型生成的时候开启优先级抢占使能,允许被高优先级任务打断,具体配置可参考《ATC工具使用指南》。
● 模型在被抢占时,有一些上下文信息保存在模型的work buf中,所以不建议被抢占模型的work_buf和其它不在同一个流上的模型之间共享,否则可能会执行错误。
● 由于低优先级任务并不是被实时打断的,而是在低优先级模型融合层执行完后才被打断,所以对高优先级任务的实时性会有影响。减少低优先级模型层间融合可以一定程度缓解高优先级任务的实时性影响。
● 优先级抢占不支持模型Dump业务场景。
● 优先级抢占不支持模型Profiling业务场景,发生抢占时生成的Profiling数据可能在工具侧解析失败。
优先级提升:优先级提升是为了保障低优先级任务在等待一定时间后获得高优先级从而能得到任务执行权。优先级提升的特点如下:
● 优先级提升是基于任务的,开启优先级提升并设置超时后,任务在超时时间内没有被调度优先级会自动提升。
● 优先级提升有两种模式:优先级逐步提升和优先级最高提升;两种模式不能同时使用。
● 优先级逐步提升是指任务在超时时间没有得到调度,优先级会提升一级,一直到任务被调度结束。
● 优先级最高提升是指任务在超时时间没有得到调度,优先级会提升到最高优先级(0)。
须知
● 低优先级任务在算力够的情况下可以不用设置优先级提升,在算力不够的情况下可以设置合理的优先级逐步提升超时时间。
● 有强时间片要求的任务可以设置合理的优先级最高提升的超时时间(根据任务预期帧率)。
● 优先级提升目的是保障低优先级任务的执行权,如果有其它强时间片的任务,低优先级任务优先级提升可能对强时间片的任务的执行产生波动,请谨慎使用。
● 优先级为0的任务不支持优先级提升。
● 优先级提升场景任务达到超时提升时间时硬件会清空等待时间;任务优先级提升到0后仍得不到调度的任务会受提升超时时间影响,不建议多个优先级提升到0的任务满负荷运行。
模型优先级说明:
● 模型支持设置不同优先级/是否开启抢占低优先级任务/任务等待超时优先级提升一级/任务等待超时优先级提升到最高。
● 模型通过svp_acl_mdl_load_from_mem接口加载时,默认优先级为3,支持优先级抢占,不支持优先级提升。
● 可以通过svp_acl_mdl_load_with_config接口加载模型,设置模型优先级配置。
● 可以通过svp_acl_mdl_set_model_config接口动态设置模型优先级配置。
8.7 Profiling 性能数据采集
基本原理:
该章节下的接口用于Profiling采集性能数据,实现方式支持以下两种:
● 方式一:将采集到的Profiling数据写入文件,再使用Profiling工具解析该文件。
● 方式二:将采集到的Profiling数据解析后写入管道,由用户读入内存,再由用户调用ACL的接口获取性能数据。
实现方式详细说明请参考“ 功能及约束说明”。
示例代码1:
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
示例中,运行管理资源申请与释放、模型加载的接口调用流程、模型推理的接口调用流程、准备模型推理的输入/输出数据的接口调用流程请参见“ 异步推理+callback 回调处理”。
//1.SVP ACL资源初始化、模型加载、数据创建
//2.创建管道(UNIX操作系统下需要引用C++标准库头文件unistd.h),用于读取以及写入模型订阅的数据
int sub_fd[2];
// 读管道指针指向sub_fd[0],写管道指针指向sub_fd[1]
pipe(sub_fd);
//3.创建模型订阅的配置并且进行模型订阅
svp_acl_prof_subscribe_config *config = svp_acl_prof_create_subscribe_config(1, 0, &sub_fd[1]);
//模型订阅需要传入模型的model_id
svp_acl_prof_model_subscribe(model_id, config);
//4.实现管道读取订阅数据的函数
//4.1 自定义函数,实现从用户内存中读取订阅数据的函数
void get_model_info(void *data, uint32_t len) {
uint32_t op_number = 0;
uint32_t data_len = 0;
//通过SVP ACL接口读取算子信息个数
svp_acl_prof_get_op_num(data, len, &op_number);
//遍历用户内存的算子信息
for (int32_t i = 0; i < op_number; i++){
//获取算子的模型id
uint32_t model_id = svp_acl_prof_get_model_id(data,len, i);
//获取算子的类型名称长度
size_t op_type_len = 0;
svp_acl_prof_get_op_type_len(data, len, i, &op_type_len);
//获取算子的类型名称
char op_type[op_type_len];
svp_acl_prof_get_op_type(data, len, i, op_type, op_type_len);
//获取算子的详细名称长度
size_t op_name_len = 0;
svp_acl_prof_get_op_name_len(data, len, i, &op_name_len);
//获取算子的详细名称
char op_name[op_name_len];
svp_acl_prof_get_op_name(data, len, i, op_name, op_name_len);
//获取算子的执行开始时间
uint64_t op_start = svp_acl_prof_get_op_start(data, len, i);
//获取算子的执行结束时间
uint64_t op_end = svp_acl_prof_get_op_end(data, len, i);
uint64_t op_duration = svp_acl_prof_get_op_duration(data, len, i);
}
}
//4.2 自定义函数,实现从管道中读取数据到用户内存的函数
void *prof_data_read(void *fd) {
//设置每次从管道中读取的算子信息个数
uint64_t N = 10;
//获取单位算子信息的大小(Byte)
uint64_t buffer_size = 0;
svp_acl_prof_get_op_desc_size(&buffer_size);
//计算存储算子信息的内存的大小,并且申请内存
uint64_t read_buf_len = buffer_size * N;
char *read_buf = (char *)malloc(sizeof(char) * read_buf_len);
//从管道中读取数据到申请的内存中,读取到的实际数据大小data_len可能小于buffer_size * N,如果管道中没
有数据,默认会阻塞直到读取到数据为止
uint32_t data_len = read(*(int*)fd, read_buf, read_buf_len);
//读取数据到read_buf成功
while (data_len > 0) {
//调用4.1实现的函数解析内存中的数据
get_model_info(read_buf, data_len);
memset(read_buf, 0, buffer_size);
data_len = read(*(int*)fd, read_buf, read_buf_len);
}
free(read_buf);
}
//5. 启动线程读取管道数据并解析
pthread_t sub_tid = 0;
pthread_create(&sub_tid, NULL, prof_data_read, &sub_fd[0]);
//6.执行模型
ret = svp_acl_mdl_execute(model_id, input, output);
//7.处理模型推理结果、释放资源、卸载模型、等待线程数据获取完毕
//8.取消订阅,释放订阅相关资源
svp_acl_prof_model_unsubscribe(model_id);
close(sub_fd[1]);
pthread_join(sub_tid, NULL);
close(sub_fd[0]);
//释放config指针
svp_acl_prof_destroy_subscribe_config(config);
//9. 释放运行管理资源,SVP ACL去初始化
//......
示例代码2:
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
示例中,运行管理资源申请与释放、模型加载的接口调用流程、模型推理的接口调用流程、准备模型推理的输入/输出数据的接口调用流程请参见“ 异步推理+callback 回调处理”。
//1.SVP ACL资源初始化、模型加载、数据创建
//2.profiling初始化
//设置数据落盘路径
const char *prof_path = "./";
svp_acl_prof_init(prof_path, strlen(prof_path));
//3.进行profiling配置
uint32_t device_id_list[1] = {0};
//创建配置结构体
svp_acl_prof_config *config = svp_acl_prof_create_config(device_id_list, 1, 0, NULL,
SVP_ACL_PROF_AICORE_METRICS | SVP_ACL_PROF_AICPU);
svp_acl_prof_start(config);
//4.执行模型
ret = svp_acl_mdl_execute(model_id, input, output);
//5.处理模型推理结果、释放资源、卸载模型
//6.关闭profiling配置, 释放配置资源, 释放profiling组件资源
svp_acl_prof_stop(config);
svp_acl_prof_destroy_config(config);
svp_acl_prof_finalize();
//7.释放运行管理资源,SVP ACL去初始化.
//......
JSON文件格式:
通过svp_acl_init指定带有Profiling配置的json文件也可以实现将采集到的Profiling数据写入文件的功能,文件格式如下:
{
"profiler": {
"switch": "on",
"output": "output",
"interval": "20",
"aic_metrics": "ArithmeticUtilization",
"aicpu": "on",
"acl_api": "on"
}
}
profiler参数配置说明:
● switch:Profiling开关,取值on或off。可选参数。
on表示开启Profiling,off表示关闭Profiling;如果缺失该参数或参数值不为on,则表示关闭Profiling。
● output:Profiling性能数据在板端环境上的落盘路径。可选参数。Profiling采集结束后,在该目录下生成JOB开头目录,存放Profiling采集的性能原始数据。支持配置绝对路径或相对路径(相对执行命令行时的当前路径):
– 绝对路径配置以“/”开头,例如:/home/output。
– 相对路径配置直接以目录名开始,例如:output。
– 如果该处设置的目录不存在,默认存放采集结果数据到应用工程可执行文件所在目录(确保安装时配置的运行用户具有该目录的读写权限)
注意
该参数指定的目录需要提前创建且确保安装时配置的运行用户具有读写权限。
● interval:Profiling采集间隔,代表每interval次推理保存一次Profiling数据,此配置建议不要设置太小,否则可能由于落盘数据太慢而引起数据丢失。
● aic_metrics: 模式识别 Core采集事件,配置为ArithmeticUtilization代表采集 模式识别 Core性能数据,否则为不采集。
● aicpu: 模式识别 CPU采集事件,配置为on代表采集 模式识别 CPU性能数据,否则为不开启。
● acl_api:ACL API采集事件,配置为on代表采集主要ACL API接口性能数据,否则为不开启。
注意
多次采集的场景,acl_api会采集每次的数据然后取平均值,aic的core性能数据会根据interval采样取平均,可能会出现acl_api时间比aic时间短的现象(ms级别以下)。