昇腾 - AscendCL C++应用开发 推理部分 模型执行
flyfish
准备模型执行的输入/输出数据结构
cpp
aclmdlDataset
├── aclDataBuffer
│ ├── buffer address
│ └── buffer size
└── aclDataBuffer
├── buffer address
└── buffer size
...
使用aclmdlDataset类型的数据描述模型的输入/输出数据,模型可能存在多个输入、多个输出。
调用aclmdlDataset类型下的操作接口添加aclDataBuffer类型的数据、获取aclDataBuffer的个数等。
每个输入/输出的内存地址、内存大小用aclDataBuffer类型的数据来描述。
调用aclDataBuffer类型下的操作接口获取内存地址、内存大小等。
模型执行的输入/输出数据结构的准备流程
(编辑方法:文字前面加 - ,上下级关系按tab)
-
创建描述模型输入/输出的数据类型
- aclmdlCreateDataset
-
获取模型的输入/输出的个数
- aclmdlGetNumInputs
- aclmdlGetNumOutputs
-
循环:
-
获取每个输入/输出的内存量
- aclmdlGetInputSizeByIndex
- aclmdlGetOutputSizeByIndex
-
为模型的每个输入/输出申请内存
- aclrtMalloc
-
创建描述模型输入/输出内存的数据类型
- aclCreateDataBuffer
-
获取输入/输出的名称 (可选)
- aclmdlGetInputNameByIndex
- aclmdlGetOutputNameByIndex
-
向aclmdlDataset中添加
- aclDataBuffer
- aclmdlAddDatasetBuffer
-
ModelProc类的主要流程
1. 初始化与资源管理
当创建一个 ModelProc
对象时,它首先会做一些基础的初始化工作,比如:
-
初始化变量 :它会设置一些标志,表示模型是否已经加载、资源是否已释放等等。
-
检查设备模式 :它会检查当前设备是在电脑的内存(HOST模式)上运行,还是在设备的内存(Device模式)上运行,这样在后续的操作中可以知道数据应该放在哪里。
2. 加载模型
Load
方法是用来加载模型的:
-
检查是否已加载 :它首先会检查模型是否已经加载过,如果已经加载,就不会重复加载。
-
加载模型文件 :如果模型没有加载过,它会从指定的路径加载模型文件,并保存模型的 ID。
-
准备模型描述 :加载成功后,它会创建一个模型描述,获取有关模型输入输出的信息,比如模型需要多少输入,以及会产生多少输出。
-
分配输出内存 :它还会为每个输出分配内存,这样模型运行后就知道把结果存放在哪里。
3. 准备输入数据
在模型运行之前,需要准备好输入数据。ModelProc
提供了几种不同的 CreateInput
方法:
-
单个输入 :如果的模型只需要一个输入,可以使用一个方法将这个输入数据添加到模型的输入集里。
-
多个输入 :如果的模型需要多个输入(比如同时处理图片和文本),它有另一种方法可以将多个输入数据添加到模型中。
-
通用输入 :还有一种方法允许用一个列表传递多个输入数据,这样更加灵活。
无论哪种方式,数据都会通过
AddDatasetBuffer
方法添加到模型的输入集里,准备进行推理。
4. 执行模型推理
Execute
方法就是用来让模型"干活"的:
-
运行推理 :它会调用一个函数来执行模型推理,将输入数据进行处理。
-
获取结果 :推理完成后,它会从模型输出中取出结果,并保存到一个结构体里,供后续使用,比如显示预测结果或进行进一步分析。
5. 资源释放与模型卸载
当用完模型之后,需要释放占用的资源:
-
卸载模型 :
Unload
方法负责卸载模型,释放所有分配的资源,包括输入和输出的数据集、模型描述等等。 -
销毁数据集 :它会专门销毁输入输出的数据集,并释放缓冲区的内存,确保没有内存泄漏。
通过
ModelProc
类,可以轻松地加载模型、准备数据、执行推理并获取结果,而无需担心资源管理的问题。这个类会帮自动处理资源的分配和释放,让更专注于模型的使用本身。
模型执行 代码的封装 实现文件
cpp
/**
* File ModelProc.cpp
* Description: handle model process
*/
#include <iostream>
#include "ModelProc.h"
using namespace std;
namespace acllite {
// ModelProc 构造函数,初始化成员变量。
ModelProc::ModelProc() : loadFlag_(false), isReleased_(false),
modelId_(0), modelPath_(""), modelDesc_(nullptr),
input_(nullptr), output_(nullptr), outputsNum_(0)
{
// 获取当前设备的运行模式(CPU或Device),并将结果存储在runMode_中。
aclrtGetRunMode(&runMode_);
}
// 析构函数,在对象销毁时调用,确保资源被释放。
ModelProc::~ModelProc()
{
DestroyResource();
}
// 销毁资源,释放加载的模型和相关数据。
void ModelProc::DestroyResource()
{
if (isReleased_) {
return; // 如果资源已经释放过,则直接返回。
}
Unload(); // 卸载模型。
isReleased_ = true; // 标记资源已释放。
}
// 加载模型并初始化相关资源。
bool ModelProc::Load(const string& modelPath)
{
modelPath_.assign(modelPath.c_str()); // 保存模型路径。
if (loadFlag_) {
LOG_PRINT("[ERROR] %s is loaded already", modelPath_.c_str());
return false; // 如果模型已经加载,则返回false。
}
// 从文件加载模型,加载成功返回模型ID。
aclError aclRet = aclmdlLoadFromFile(modelPath_.c_str(), &modelId_);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Load model(%s) from file return %d", modelPath_.c_str(), aclRet);
return false; // 加载失败,返回false。
}
loadFlag_ = true; // 标记模型已成功加载。
// 创建模型描述结构体,用于描述模型的基本信息。
modelDesc_ = aclmdlCreateDesc();
if (modelDesc_ == nullptr) {
LOG_PRINT("[ERROR] Create model(%s) description failed", modelPath_.c_str());
return false; // 创建失败,返回false。
}
// 获取模型描述信息。
aclRet = aclmdlGetDesc(modelDesc_, modelId_);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Get model(%s) description failed", modelPath_.c_str());
return false; // 获取失败,返回false。
}
if (modelDesc_ == nullptr) {
LOG_PRINT("[ERROR] Create output failed for no model(%s) description", modelPath_.c_str());
return false; // 如果模型描述为空,返回false。
}
// 创建用于存放模型输出数据的结构体。
output_ = aclmdlCreateDataset();
if (output_ == nullptr) {
LOG_PRINT("[ERROR] Create output failed for create dataset error");
return false; // 创建失败,返回false。
}
// 获取模型输出的数量。
outputsNum_ = aclmdlGetNumOutputs(modelDesc_);
for (size_t i = 0; i < outputsNum_; ++i) {
// 获取模型第i个输出的内存大小。
size_t bufSize = aclmdlGetOutputSizeByIndex(modelDesc_, i);
void *outputBuffer = nullptr;
// 在Device上分配内存,用于存放模型输出数据。
aclRet = aclrtMalloc(&outputBuffer, bufSize, ACL_MEM_MALLOC_NORMAL_ONLY);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Create output failed for malloc device failed, size %d", (int)bufSize);
return false; // 内存分配失败,返回false。
}
// 创建aclDataBuffer,用于描述输出数据的内存信息。
aclDataBuffer* dataBuf = aclCreateDataBuffer(outputBuffer, bufSize);
if (dataBuf == nullptr) {
LOG_PRINT("[ERROR] Create data buffer error");
return false; // 创建失败,返回false。
}
// 将数据缓冲区添加到输出数据集中。
aclRet = aclmdlAddDatasetBuffer(output_, dataBuf);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Add dataset buffer error %d", aclRet);
aclRet = aclDestroyDataBuffer(dataBuf);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Destroy dataset buffer error %d", aclRet);
}
dataBuf = nullptr;
return false; // 添加失败,返回false。
}
// 保存输出缓冲区信息,用于后续处理。
outputBuf_.push_back(outputBuffer);
outputSize_.push_back(bufSize);
outputDataBuf_.push_back(dataBuf);
}
LOG_PRINT("[INFO] Load model %s success", modelPath_.c_str());
return true; // 加载成功,返回true。
}
// 创建输入数据,单输入版本。
bool ModelProc::CreateInput(void *input, uint32_t size)
{
vector<DataInfo> inputData = {{input, size}};
return CreateInput(inputData); // 调用多输入版本的CreateInput。
}
// 创建输入数据,多输入版本。
bool ModelProc::CreateInput(void *input1, uint32_t input1size,
void* input2, uint32_t input2size)
{
vector<DataInfo> inputData = {{input1, input1size}, {input2, input2size}};
return CreateInput(inputData); // 调用多输入版本的CreateInput。
}
// 创建输入数据,通用版本,支持多个输入。
bool ModelProc::CreateInput(vector<DataInfo>& inputData)
{
// 获取模型的输入数量。
uint32_t dataNum = aclmdlGetNumInputs(modelDesc_);
if (dataNum == 0) {
LOG_PRINT("[ERROR] Create input failed for no input data");
return false; // 如果模型没有输入,返回false。
}
if (dataNum != inputData.size()) {
LOG_PRINT("[ERROR] Create input failed for wrong input nums");
return false; // 输入数量不匹配,返回false。
}
// 创建输入数据集。
input_ = aclmdlCreateDataset();
if (input_ == nullptr) {
LOG_PRINT("[ERROR] Create input failed for create dataset failed");
return false; // 创建失败,返回false。
}
// 将输入数据缓冲区添加到数据集中。
for (uint32_t i = 0; i < inputData.size(); i++) {
size_t modelInputSize = aclmdlGetInputSizeByIndex(modelDesc_, i);
if (modelInputSize != inputData[i].size) {
LOG_PRINT("[WARNING] Input size verify failed input[%d] size: %ld, provide size : %d", i, modelInputSize, inputData[i].size);
}
bool ret = AddDatasetBuffer(input_, inputData[i].data, inputData[i].size);
if (!ret) {
LOG_PRINT("[ERROR] Create input failed for add dataset buffer error %d", ret);
return false; // 添加失败,返回false。
}
}
return true; // 创建输入成功,返回true。
}
// 添加数据缓冲区到数据集中。
bool ModelProc::AddDatasetBuffer(aclmdlDataset *dataset, void* buffer, uint32_t bufferSize)
{
// 创建aclDataBuffer,用于描述缓冲区。
aclDataBuffer* dataBuf = aclCreateDataBuffer(buffer, bufferSize);
if (dataBuf == nullptr) {
LOG_PRINT("[ERROR] Create data buffer error");
return false; // 创建失败,返回false。
}
// 将数据缓冲区添加到数据集中。
aclError ret = aclmdlAddDatasetBuffer(dataset, dataBuf);
if (ret != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Add dataset buffer error %d", ret);
ret = aclDestroyDataBuffer(dataBuf);
if (ret != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Destroy dataset buffer error %d", ret);
}
dataBuf = nullptr;
return false; // 添加失败,返回false。
}
return true; // 添加成功,返回true。
}
// 执行模型推理,并获取推理结果。
bool ModelProc::Execute(vector<InferenceOutput>& inferOutputs)
{
// 执行模型推理。
aclError aclRet = aclmdlExecute(modelId_, input_, output_);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Execute model(%s) error:%d", modelPath_.c_str(), aclRet);
return false; // 执行失败,返回false。
}
// 获取推理结果并保存到inferOutputs中。
for (uint32_t i = 0; i < outputsNum_; i++) {
InferenceOutput out;
bool ret = GetOutputItem(out, i);
if (!ret) {
LOG_PRINT("[ERROR] Get the %dth interference output failed, error: %d", i, ret);
return ret; // 获取输出失败,返回false。
}
inferOutputs.push_back(out);
}
return true; // 执行成功,返回true。
}
// 获取单个输出项。
bool ModelProc::GetOutputItem(InferenceOutput& out, uint32_t idx)
{
// 获取第idx个输出缓冲区的aclDataBuffer。
aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, idx);
if (dataBuffer == nullptr) {
LOG_PRINT("[ERROR] Get model output buffer failed");
return false; // 获取失败,返回false。
}
// 获取缓冲区内存指针。
void* data = aclGetDataBufferAddr(dataBuffer);
if (data == nullptr) {
LOG_PRINT("[ERROR] Get model output data buffer address failed");
return false; // 获取失败,返回false。
}
// 获取缓冲区大小。
uint32_t len = aclGetDataBufferSize(dataBuffer);
out.data = data; // 将输出数据指针保存到out中。
out.size = len; // 将输出数据大小保存到out中。
return true; // 获取成功,返回true。
}
// 卸载模型并释放相关资源。
void ModelProc::Unload()
{
if (!loadFlag_) {
return; // 如果模型未加载,则直接返回。
}
// 销毁模型输入数据集。
if (input_ != nullptr) {
int ret = DestroyDataset(input_);
if (ret != 0) {
LOG_PRINT("[ERROR] Unload model(%s) error for destroy input dataset failed", modelPath_.c_str());
}
}
// 销毁模型输出数据集。
if (output_ != nullptr) {
int ret = DestroyDataset(output_);
if (ret != 0) {
LOG_PRINT("[ERROR] Unload model(%s) error for destroy output dataset failed", modelPath_.c_str());
}
}
// 销毁模型描述结构体。
if (modelDesc_ != nullptr) {
aclError ret = aclmdlDestroyDesc(modelDesc_);
if (ret != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Unload model(%s) error for destroy modelDesc failed, modelDesc_:%p, ret:%d", modelPath_.c_str(), modelDesc_, ret);
}
modelDesc_ = nullptr;
}
// 卸载模型。
aclError ret = aclmdlUnload(modelId_);
if (ret != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Unload model(%s) error for unload failed, modelId_:%u, ret:%d", modelPath_.c_str(), modelId_, ret);
}
modelId_ = 0;
// 清除模型加载标记。
loadFlag_ = false;
}
// 销毁数据集。
int ModelProc::DestroyDataset(aclmdlDataset *dataset)
{
aclError aclRet;
// 遍历数据集中的每个缓冲区,并释放对应的内存。
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(dataset); ++i) {
aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(dataset, i);
if (dataBuffer == nullptr) {
LOG_PRINT("[ERROR] Get data buffer error");
continue;
}
void* data = aclGetDataBufferAddr(dataBuffer);
if (data == nullptr) {
LOG_PRINT("[ERROR] Get data buffer address error");
continue;
}
// 释放Device上的内存。
aclRet = aclrtFree(data);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Free device memory failed");
}
// 销毁数据缓冲区。
aclRet = aclDestroyDataBuffer(dataBuffer);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Destroy data buffer failed");
}
}
// 销毁数据集。
aclRet = aclmdlDestroyDataset(dataset);
if (aclRet != ACL_SUCCESS) {
LOG_PRINT("[ERROR] Destroy dataset failed");
return -1; // 销毁失败,返回错误码。
}
return 0; // 销毁成功,返回0。
}
} // namespace acllite
模型执行 代码的封装 头文件
cpp
/**
* File ModelProc.h
* Description: 处理模型推理过程的头文件
*/
#ifndef MODEL_PROC_H
#define MODEL_PROC_H
#pragma once
#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "acllite_common/Common.h"
namespace acllite {
/**
* @class ModelProc
* @brief 用于加载模型、创建输入输出、执行推理并释放资源的类
*/
class ModelProc {
public:
/**
* @brief 构造函数,初始化类成员变量
*/
ModelProc();
/**
* @brief 析构函数,释放资源
*/
~ModelProc();
/**
* @brief 销毁模型处理过程中的资源
*/
void DestroyResource();
/**
* @brief 加载指定路径下的模型
* @param modelPath 模型文件的路径
* @return 成功返回true,失败返回false
*/
bool Load(const std::string& modelPath);
/**
* @brief 创建单一输入的数据集
* @param input 输入数据的指针
* @param size 输入数据的大小
* @return 成功返回true,失败返回false
*/
bool CreateInput(void* input, uint32_t size);
/**
* @brief 创建包含两个输入的数据集
* @param input1 第一个输入数据的指针
* @param input1size 第一个输入数据的大小
* @param input2 第二个输入数据的指针
* @param input2size 第二个输入数据的大小
* @return 成功返回true,失败返回false
*/
bool CreateInput(void* input1, uint32_t input1size,
void* input2, uint32_t input2size);
/**
* @brief 从多个输入数据创建输入数据集
* @param inputData 包含多个输入数据的向量
* @return 成功返回true,失败返回false
*/
bool CreateInput(std::vector<DataInfo>& inputData);
/**
* @brief 执行模型推理
* @param inferOutputs 用于存储推理输出的向量
* @return 成功返回true,失败返回false
*/
bool Execute(std::vector<InferenceOutput>& inferOutputs);
/**
* @brief 销毁输入数据集
*/
void DestroyInput();
/**
* @brief 销毁输出数据集
*/
void DestroyOutput();
/**
* @brief 卸载模型,释放相关资源
*/
void Unload();
private:
/**
* @brief 向数据集添加缓冲区
* @param dataset 数据集指针
* @param buffer 缓冲区指针
* @param bufferSize 缓冲区大小
* @return 成功返回true,失败返回false
*/
bool AddDatasetBuffer(aclmdlDataset* dataset,
void* buffer, uint32_t bufferSize);
/**
* @brief 获取推理输出项
* @param out 存储输出项的结构
* @param idx 输出项的索引
* @return 成功返回true,失败返回false
*/
bool GetOutputItem(InferenceOutput& out, uint32_t idx);
private:
aclrtRunMode runMode_; ///< 运行模式(HOST或Device模式)
bool loadFlag_; ///< 模型是否已加载的标志
bool isReleased_; ///< 资源是否已释放的标志
uint32_t modelId_; ///< 模型ID
std::string modelPath_; ///< 模型文件路径
aclmdlDesc *modelDesc_; ///< 模型描述符指针
aclmdlDataset *input_; ///< 输入数据集指针
aclmdlDataset *output_; ///< 输出数据集指针
size_t outputsNum_; ///< 输出数据项的数量
std::vector<void*> outputBuf_; ///< 输出缓冲区指针向量
std::vector<size_t> outputSize_; ///< 输出数据大小向量
std::vector<aclDataBuffer*> outputDataBuf_; ///< 输出数据缓冲区指针向量
};
} // namespace acllite
#endif // MODEL_PROC_H