昇腾CANN实战:手把手教你用AscendCL写一个图片分类推理服务(C++完整代码)
·
昇腾CANN实战:从零构建图片分类推理服务的完整指南
1. 环境准备与工具链配置
在开始构建图片分类推理服务之前,我们需要确保开发环境正确配置。昇腾AI处理器的开发环境与传统GPU开发有所不同,需要特别注意以下几个方面:
基础环境要求:
- 操作系统:Ubuntu 18.04/20.04 LTS 或 CentOS 7.6/8.2
- 编译器:GCC 7.3.0或更高版本
- CMake:3.12或更高版本
- Python:3.7.x(用于部分工具链)
昇腾软件栈安装:
- 下载CANN工具包(以5.1.RC1版本为例):
wget https://ascend-repo.xxx.com/Ascend-cann-toolkit_5.1.RC1_linux-aarch64.run
- 安装工具包:
chmod +x Ascend-cann-toolkit_5.1.RC1_linux-aarch64.run
./Ascend-cann-toolkit_5.1.RC1_linux-aarch64.run --install
- 设置环境变量:
source /usr/local/Ascend/ascend-toolkit/set_env.sh
关键环境变量说明:
| 变量名 | 路径示例 | 作用 |
|---|---|---|
| DDK_PATH | /usr/local/Ascend/ascend-toolkit/latest | 开发工具包路径 |
| NPU_HOST_LIB | /usr/local/Ascend/ascend-toolkit/latest/acllib/lib64/stub | 运行时库路径 |
| LD_LIBRARY_PATH | $DDK_PATH/acllib/lib64:$LD_LIBRARY_PATH | 动态库搜索路径 |
| PATH | $DDK_PATH/bin:$PATH | 可执行文件路径 |
注意:实际路径可能因安装版本和目录不同而变化,请根据实际情况调整。
2. 项目结构与CMake配置
一个典型的昇腾推理项目应包含以下目录结构:
resnet_classifier/
├── CMakeLists.txt
├── include/
│ ├── model_process.h
│ ├── utils.h
├── src/
│ ├── main.cpp
│ ├── model_process.cpp
│ ├── utils.cpp
├── model/
│ └── resnet50.om
└── data/
└── test_image.jpg
CMakeLists.txt关键配置:
cmake_minimum_required(VERSION 3.12)
project(resnet_classifier)
set(CMAKE_CXX_STANDARD 11)
# 查找AscendCL库
find_library(ACL_LIBRARY ascendcl PATHS $ENV{NPU_HOST_LIB} REQUIRED)
find_library(DVPP_LIBRARY acl_dvpp PATHS $ENV{NPU_HOST_LIB} REQUIRED)
# 包含目录
include_directories(
$ENV{DDK_PATH}/acllib/include
${PROJECT_SOURCE_DIR}/include
)
# 可执行文件
add_executable(resnet_classifier
src/main.cpp
src/model_process.cpp
src/utils.cpp
)
# 链接库
target_link_libraries(resnet_classifier
${ACL_LIBRARY}
${DVPP_LIBRARY}
pthread
)
# 安装目标
install(TARGETS resnet_classifier DESTINATION bin)
3. 核心组件实现
3.1 资源初始化模块
资源管理是昇腾应用开发的基础,正确的初始化顺序至关重要:
// model_process.h
class ModelProcessor {
public:
ModelProcessor();
~ModelProcessor();
bool Init();
bool LoadModel(const std::string& model_path);
bool CreateInputOutput();
bool Execute();
bool ProcessOutput();
private:
int32_t deviceId_ = 0;
aclrtContext context_;
aclrtStream stream_;
uint32_t modelId_;
aclmdlDesc* modelDesc_;
aclmdlDataset* input_;
aclmdlDataset* output_;
};
初始化流程代码:
bool ModelProcessor::Init() {
// 1. 初始化ACL
aclError ret = aclInit(nullptr);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to init acl, error: " << ret << std::endl;
return false;
}
// 2. 设置运算设备
ret = aclrtSetDevice(deviceId_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to set device, error: " << ret << std::endl;
return false;
}
// 3. 创建Context
ret = aclrtCreateContext(&context_, deviceId_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to create context, error: " << ret << std::endl;
return false;
}
// 4. 创建Stream
ret = aclrtCreateStream(&stream_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to create stream, error: " << ret << std::endl;
return false;
}
return true;
}
3.2 模型加载与输入输出准备
模型加载需要特别注意内存管理:
bool ModelProcessor::LoadModel(const std::string& model_path) {
// 1. 加载模型文件
aclError ret = aclmdlLoadFromFile(model_path.c_str(), &modelId_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to load model, error: " << ret << std::endl;
return false;
}
// 2. 创建模型描述
modelDesc_ = aclmdlCreateDesc();
ret = aclmdlGetDesc(modelDesc_, modelId_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to get model desc, error: " << ret << std::endl;
return false;
}
return true;
}
输入输出数据结构准备:
bool ModelProcessor::CreateInputOutput() {
// 1. 创建输入数据集
input_ = aclmdlCreateDataset();
size_t input_size = aclmdlGetInputSizeByIndex(modelDesc_, 0);
void* input_buffer = nullptr;
aclError ret = aclrtMalloc(&input_buffer, input_size, ACL_MEM_MALLOC_HUGE_FIRST);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to malloc input buffer, error: " << ret << std::endl;
return false;
}
aclDataBuffer* input_data = aclCreateDataBuffer(input_buffer, input_size);
ret = aclmdlAddDatasetBuffer(input_, input_data);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to add input buffer, error: " << ret << std::endl;
return false;
}
// 2. 创建输出数据集(类似输入处理)
// ...
return true;
}
3.3 图片预处理与内存管理
图片预处理是计算机视觉应用的关键环节:
// utils.cpp
bool Utils::LoadImageToDevice(const std::string& image_path,
void*& device_buffer,
size_t& buffer_size) {
// 1. 读取图片文件
std::ifstream file(image_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Failed to open image file" << std::endl;
return false;
}
buffer_size = file.tellg();
file.seekg(0, std::ios::beg);
// 2. 分配Host内存
void* host_buffer = nullptr;
aclError ret = aclrtMallocHost(&host_buffer, buffer_size);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to malloc host buffer, error: " << ret << std::endl;
return false;
}
file.read(static_cast<char*>(host_buffer), buffer_size);
file.close();
// 3. 分配Device内存并拷贝数据
ret = aclrtMalloc(&device_buffer, buffer_size, ACL_MEM_MALLOC_HUGE_FIRST);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to malloc device buffer, error: " << ret << std::endl;
aclrtFreeHost(host_buffer);
return false;
}
ret = aclrtMemcpy(device_buffer, buffer_size, host_buffer,
buffer_size, ACL_MEMCPY_HOST_TO_DEVICE);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to copy data to device, error: " << ret << std::endl;
aclrtFree(device_buffer);
aclrtFreeHost(host_buffer);
return false;
}
// 4. 释放Host内存
aclrtFreeHost(host_buffer);
return true;
}
4. 推理执行与结果处理
4.1 同步推理流程
bool ModelProcessor::Execute() {
// 1. 执行推理
aclError ret = aclmdlExecute(modelId_, input_, output_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to execute model, error: " << ret << std::endl;
return false;
}
// 2. 同步等待Stream完成
ret = aclrtSynchronizeStream(stream_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to sync stream, error: " << ret << std::endl;
return false;
}
return true;
}
4.2 输出结果解析
分类模型的输出通常是概率分布,需要解析为可读结果:
bool ModelProcessor::ProcessOutput() {
// 1. 获取输出缓冲区和大小
aclDataBuffer* output_data = aclmdlGetDatasetBuffer(output_, 0);
void* output_buffer = aclGetDataBufferAddr(output_data);
size_t output_size = aclGetDataBufferSizeV2(output_data);
// 2. 分配Host内存并拷贝结果
void* host_buffer = nullptr;
aclError ret = aclrtMallocHost(&host_buffer, output_size);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to malloc host output buffer, error: " << ret << std::endl;
return false;
}
ret = aclrtMemcpy(host_buffer, output_size, output_buffer,
output_size, ACL_MEMCPY_DEVICE_TO_HOST);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to copy output data, error: " << ret << std::endl;
aclrtFreeHost(host_buffer);
return false;
}
// 3. 解析输出为分类结果
float* probabilities = static_cast<float*>(host_buffer);
size_t class_count = output_size / sizeof(float);
std::vector<std::pair<float, int>> results;
for (int i = 0; i < class_count; ++i) {
results.emplace_back(probabilities[i], i);
}
// 按置信度排序
std::sort(results.begin(), results.end(),
[](const auto& a, const auto& b) { return a.first > b.first; });
// 打印Top-5结果
std::cout << "Top-5 classification results:" << std::endl;
for (int i = 0; i < 5 && i < results.size(); ++i) {
std::cout << " Class " << results[i].second << ": "
<< results[i].first * 100 << "%" << std::endl;
}
// 4. 释放资源
aclrtFreeHost(host_buffer);
return true;
}
5. 完整应用示例
5.1 主程序流程
// main.cpp
int main(int argc, char** argv) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <model_path> <image_path>" << std::endl;
return 1;
}
ModelProcessor processor;
// 1. 初始化资源
if (!processor.Init()) {
std::cerr << "Failed to initialize resources" << std::endl;
return 1;
}
// 2. 加载模型
if (!processor.LoadModel(argv[1])) {
std::cerr << "Failed to load model" << std::endl;
return 1;
}
// 3. 准备输入输出
if (!processor.CreateInputOutput()) {
std::cerr << "Failed to create input/output" << std::endl;
return 1;
}
// 4. 加载图片到设备
void* image_buffer = nullptr;
size_t image_size = 0;
if (!Utils::LoadImageToDevice(argv[2], image_buffer, image_size)) {
std::cerr << "Failed to load image" << std::endl;
return 1;
}
// 5. 设置模型输入
if (!processor.SetInput(image_buffer, image_size)) {
std::cerr << "Failed to set input" << std::endl;
aclrtFree(image_buffer);
return 1;
}
// 6. 执行推理
if (!processor.Execute()) {
std::cerr << "Failed to execute model" << std::endl;
aclrtFree(image_buffer);
return 1;
}
// 7. 处理输出
if (!processor.ProcessOutput()) {
std::cerr << "Failed to process output" << std::endl;
aclrtFree(image_buffer);
return 1;
}
// 8. 释放资源
aclrtFree(image_buffer);
return 0;
}
5.2 编译与运行
编译命令:
mkdir build && cd build
cmake ..
make -j$(nproc)
运行示例:
./resnet_classifier model/resnet50.om data/test_image.jpg
预期输出示例:
Top-5 classification results:
Class 232: 98.76%
Class 123: 0.87%
Class 456: 0.21%
Class 789: 0.09%
Class 101: 0.07%
6. 高级特性与优化
6.1 异步推理实现
bool ModelProcessor::ExecuteAsync() {
// 1. 执行异步推理
aclError ret = aclmdlExecuteAsync(modelId_, input_, output_, stream_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to execute async model, error: " << ret << std::endl;
return false;
}
// 2. 注册回调函数
ret = aclrtLaunchCallback([](void* userdata) {
auto self = static_cast<ModelProcessor*>(userdata);
self->ProcessOutput();
}, this, ACL_CALLBACK_BLOCK, stream_);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to set callback, error: " << ret << std::endl;
return false;
}
return true;
}
6.2 动态Batch处理
bool ModelProcessor::SetDynamicBatchSize(int batch_size) {
// 1. 检查模型是否支持动态batch
aclmdlBatch batch_info;
aclError ret = aclmdlGetDynamicBatch(modelDesc_, &batch_info);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Model doesn't support dynamic batch" << std::endl;
return false;
}
// 2. 设置当前batch大小
ret = aclmdlSetDynamicBatchSize(modelId_, input_, 0, batch_size);
if (ret != ACL_ERROR_NONE) {
std::cerr << "Failed to set dynamic batch size, error: " << ret << std::endl;
return false;
}
return true;
}
6.3 性能优化技巧
- 内存池技术:
class MemoryPool {
public:
void* AllocDevice(size_t size) {
if (auto it = pool_.find(size); it != pool_.end() && !it->second.empty()) {
void* ptr = it->second.top();
it->second.pop();
return ptr;
}
void* ptr = nullptr;
aclrtMalloc(&ptr, size, ACL_MEM_MALLOC_HUGE_FIRST);
return ptr;
}
void FreeDevice(void* ptr, size_t size) {
pool_[size].push(ptr);
}
private:
std::unordered_map<size_t, std::stack<void*>> pool_;
};
- Stream并行化:
bool ModelProcessor::ParallelInference() {
// 创建多个Stream
aclrtStream stream1, stream2;
aclrtCreateStream(&stream1);
aclrtCreateStream(&stream2);
// 在不同的Stream上并行执行任务
aclrtMemcpyAsync(dev_ptr1, size, host_ptr1, size,
ACL_MEMCPY_HOST_TO_DEVICE, stream1);
aclrtMemcpyAsync(dev_ptr2, size, host_ptr2, size,
ACL_MEMCPY_HOST_TO_DEVICE, stream2);
// ...
}
- 模型量化与优化:
atc --model=resnet50.onnx --framework=5 --output=resnet50_quant \
--soc_version=Ascend310 --input_format=NCHW \
--precision_mode=allow_fp32_to_fp16 \
--fusion_switch_file=fusion_switch.cfg
更多推荐


所有评论(0)