本文简要介绍ResNet核心原理,并展示在昇腾平台上使用C++实现模型推理的完整流程。

ResNet核心原理

ResNet(残差网络)通过引入残差块跳跃连接,解决了深度网络训练中的梯度消失问题。

残差学习思想:让网络学习目标映射与输入之间的残差F(x) = H(x) - x,而不是直接学习目标映射H(x)。

昇腾平台C++推理实现

环境准备

bash

export DDK_PATH=/usr/local/Ascend/ascend-toolkit/latest
export NPU_HOST_LIB=$DDK_PATH/runtime/lib64/stub

完整代码实现

cpp

#include "acl/acl.h"
#include <iostream>
#include <fstream>
#include <map>

using namespace std;

// 全局变量
int32_t deviceId = 0;
uint32_t modelId;
size_t pictureDataSize = 0;
void* pictureHostData = nullptr;
void* pictureDeviceData = nullptr;

// 初始化资源
void InitResource()
{
    aclError ret = aclInit(nullptr);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to init ACL" << std::endl;
        return;
    }
    
    ret = aclrtSetDevice(deviceId);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to set device" << std::endl;
        return;
    }
}

// 加载模型
void LoadModel(const char* modelPath)
{
    aclError ret = aclmdlLoadFromFile(modelPath, &modelId);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to load model" << std::endl;
        return;
    }
}

// 加载图片数据
void LoadPicture(const char* picturePath)
{
    // 读取图片到Host内存
    ifstream binFile(picturePath, ifstream::binary);
    if (!binFile.is_open()) {
        std::cerr << "Failed to open picture file" << std::endl;
        return;
    }
    
    binFile.seekg(0, binFile.end);
    pictureDataSize = binFile.tellg();
    binFile.seekg(0, binFile.beg);
    
    aclError ret = aclrtMallocHost(&pictureHostData, pictureDataSize);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to malloc host memory" << std::endl;
        return;
    }
    
    binFile.read(static_cast<char*>(pictureHostData), pictureDataSize);
    binFile.close();
    
    // 复制到Device内存
    ret = aclrtMalloc(&pictureDeviceData, pictureDataSize, ACL_MEM_MALLOC_HUGE_FIRST);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to malloc device memory" << std::endl;
        return;
    }
    
    ret = aclrtMemcpy(pictureDeviceData, pictureDataSize, 
                      pictureHostData, pictureDataSize, 
                      ACL_MEMCPY_HOST_TO_DEVICE);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to copy data to device" << std::endl;
    }
}

// 执行推理
void Inference()
{
    aclmdlDesc* modelDesc = aclmdlCreateDesc();
    aclError ret = aclmdlGetDesc(modelDesc, modelId);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to get model description" << std::endl;
        return;
    }
    
    // 准备输入
    aclmdlDataset* inputDataset = aclmdlCreateDataset();
    aclDataBuffer* inputData = aclCreateDataBuffer(pictureDeviceData, pictureDataSize);
    aclmdlAddDatasetBuffer(inputDataset, inputData);
    
    // 准备输出
    aclmdlDataset* outputDataset = aclmdlCreateDataset();
    size_t outputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
    void* outputDeviceBuffer = nullptr;
    aclrtMalloc(&outputDeviceBuffer, outputSize, ACL_MEM_MALLOC_HUGE_FIRST);
    aclDataBuffer* outputData = aclCreateDataBuffer(outputDeviceBuffer, outputSize);
    aclmdlAddDatasetBuffer(outputDataset, outputData);
    
    // 执行推理
    ret = aclmdlExecute(modelId, inputDataset, outputDataset);
    if (ret != ACL_SUCCESS) {
        std::cerr << "Failed to execute model" << std::endl;
    }
    
    // 处理输出结果
    void* outputHostBuffer = nullptr;
    aclrtMallocHost(&outputHostBuffer, outputSize);
    
    // 这里简化处理,实际需要从Device复制结果到Host
    // 并解析分类结果
    
    // 释放资源
    aclmdlDestroyDataset(inputDataset);
    aclmdlDestroyDataset(outputDataset);
    aclmdlDestroyDesc(modelDesc);
}

// 资源清理
void DestroyResource()
{
    if (pictureDeviceData != nullptr) {
        aclrtFree(pictureDeviceData);
    }
    if (pictureHostData != nullptr) {
        aclrtFreeHost(pictureHostData);
    }
    
    aclmdlUnload(modelId);
    aclrtResetDevice(deviceId);
    aclFinalize();
}

int main()
{
    // 完整推理流程
    InitResource();
    LoadModel("../model/resnet50.om");
    LoadPicture("../data/test_image.bin");
    Inference();
    DestroyResource();
    
    return 0;
}

CMakeLists.txt

cmake

make_minimum_required(VERSION 3.10)
project(ResNetInference)

set(CMAKE_CXX_STANDARD 11)

include_directories(/usr/local/Ascend/ascend-toolkit/latest/include)
link_directories(/usr/local/Ascend/ascend-toolkit/latest/runtime/lib64/stub)

add_executable(main main.cpp)
target_link_libraries(main ascendcl stdc++)

关键要点

  1. 初始化流程:ACL初始化 → 设置设备 → 加载模型

  2. 数据处理:Host内存申请 → 数据读取 → Device内存复制

  3. 推理执行:准备输入输出 → 执行模型 → 处理结果

  4. 资源清理:内存释放 → 模型卸载 → ACL去初始化

总结

本文提供了ResNet在昇腾平台上C++推理的完整实现方案,涵盖从原理到代码的各个环节,为深度学习模型部署提供实用参考。

025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。

报名链接:https://www.hiascend.com/developer/activities/cann20252

注:实际使用时请根据具体模型调整数据预处理和后处理逻辑。

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐