华为的Atlas系列硬件是基于昇腾系列(HUAWEI Ascend)AI 处理器,通过模块、标卡、小站、服务器、集群等丰富的产品形态,打造面向 “ 端、边、云 ” 的全场景AI基础设施方案。在这次的极市与昇腾举行的【Atlas移植营】中,体验了将YOLOv5模型移植到Atlas设备这一过程,期间收获很多。
开发流程
开发流程包括:视频和图片资源的硬件解码、对输入图片的预处理、推理过程以及对推理输出的后处理。整体流程如下所示:

视觉与处理模块(VPC),可以实现硬件加速的图片抠图(crop)、缩放(resize)、粘贴(paste)等功能。比通用视觉算法库OpenCV要快,能够充分利用硬件性能。处理流程如下所示:

ATC模型转换
昇腾张量编译器(Ascend Tensor Compiler,简称ATC)是昇腾CANN架构体系下的模型转换工具, 它可以将开源框架的网络模型或Ascend IR定义的单算子描述文件(json格式)转换为昇腾AI处理器支持的.om格式离线模型。其功能架构如下所示:

将PyTorch模型转换为Json文件,可使用如下脚本:
C++ API
华为的Atlas系列硬件是基于昇腾系列(HUAWEI Ascend)AI 处理器,通过模块、标卡、小站、服务器、集群等丰富的产品形态,打造面向 “ 端、边、云 ” 的全场景AI基础设施方案。在这次的极市与昇腾举行的【Atlas移植营】中,体验了将YOLOv5模型移植到Atlas设备这一过程,期间收获很多。
开发流程
开发流程包括:视频和图片资源的硬件解码、对输入图片的预处理、推理过程以及对推理输出的后处理。整体流程如下所示:
视觉与处理模块(VPC),可以实现硬件加速的图片抠图(crop)、缩放(resize)、粘贴(paste)等功能。比通用视觉算法库OpenCV要快,能够充分利用硬件性能。处理流程如下所示:
ATC模型转换
昇腾张量编译器(Ascend Tensor Compiler,简称ATC)是昇腾CANN架构体系下的模型转换工具, 它可以将开源框架的网络模型或Ascend IR定义的单算子描述文件(json格式)转换为昇腾AI处理器支持的.om格式离线模型。其功能架构如下所示:
将PyTorch模型转换为Json文件,可使用如下脚本:
C++ API
#include "acl/acl.h" #include "acl/ops/acl_dvpp.h" // 系统配置 aclError ret = aclInit(); // AscendCL 初始化函数 ret = aclFinalize(); // AscendCL去初始化函数,用于释放进程内的AscendCL相关资源 // 模型加载流程 uint32_t device_count; ret = aclrtGetDeviceCount(device_count); // 获取可用Device的数量 uint32_t deviceid = 0; ret = aclrtSetDevice(deviceid); // 指定当前进程或线程中用于运算的Device // 在当前进程或线程中显式创建一个Context aclrtContext context; ret = aclrtCreatecontext(&context,deviceid); // 在当前进程或线程中创建一个Stream aclrtstream stream; ret = aclrtCreateStream(&stream); // 获取当前昇腾AI软件栈的运行模式,分为HOST端模式和DEVICE端模式两种 aclrtRunMode runmode; ret = aclrtGetRunMode(&runmode); // 从文件加载离线模型数据(适配昇腾AI处理器的离线模型) int modelIdl; model_file = "***.om"; ret = aclmdlLoadFromFileWithMem(strModelName.c_str(), &mModelID, mModelMptr, mModelMSize, mModelWptr, mModelWSize); // modelDesc 模型描述结构体,其中包括模型输入端个数,输出端个数等信息 aclmdlDesc *modelDesc model = aclmdlCreateDesc(); ret = aclmdlGetDesc(modelDesc,modelId); // **析构流程** // 析构顺序为 用modelid卸载模型、析构modelDesc、重置推理卡及关闭acl服务 ret = aclmdlUnload(modelId); ret = aclmdlDestroyDesc(modelDesc); ret = aclrtResetDevice(deviceId_); ret = aclFinalize(); // 理论上后两步不应该在推理引擎内,而应该在调度器上完成 // 模型推理流程 // **创建流程** // 模型的推理输入需要每次进行创建,推理输出的buffer可以一次性创建并一直复用 //// 创建输入 size_t modelInputSize; void *modelInputBuffer = nullptr; modelInputSize = aclmdlGetInputSizeByIndex(modelDesc, 0); // 申请一块HOST侧的内存 ret = aclrtMalloc(&modelInputBuffer, modelInputSize, ACL_MEM_MALLOC_NORMAL_ONLY); // 创建aclmdlDataset类型的数据,描述模型推理的输入,input_为aclmdlDataset类型 // 利用之前创建的HOST端内存,在DEVICE侧挂载,然后把自己的数据赋值到该块内存就完成的模型输入准备 aclmdlDataset *input_; input_ = aclmdlCreateDataset(); aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize); ret = aclmdlAddDatasetBuffer(input_, inputData); ret = aclrtMemcpy(modelInputBuffer, modelInputSize, input_host_memory_+i*3*yolo_params_.INPUT_H*yolo_params_.INPUT_W,yolo_params_.INPUT_H*yolo_params_.INPUT_W*3*sizeof(float), ACL_MEMCPY_DEVICE_TO_DEVICE); //// 创建输出 aclmdlDataset *output_; size_t outputSize = aclmdlGetNumOutputs(modelDesc); output_ = aclmdlCreateDataset(); // 循环为每个输出申请内存,并将每个输出添加到aclmdlDataset类型的数据中 // 动态batch下,output为按最大batch值创建的内存块 for (size_t i = 0; i < outputSize; ++i) { size_t buffer_size = aclmdlGetOutputSizeByIndex(modelDesc, i); void *outputBuffer = nullptr; ret = aclrtMalloc(&outputBuffer, buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY); if(ret != 0) { return -2; } aclDataBuffer* outputData = aclCreateDataBuffer(outputBuffer, buffer_size); ret = aclmdlAddDatasetBuffer(output_, outputData); if(ret != 0) { return -2; } } //// 执行推理 ret = aclmdlExecute(modelId, input_, output_); //// 获取输出到host侧 aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, idx); // 获取buffer地址 void* dataBufferDev = aclGetDataBufferAddr(dataBuffer); // 获取buffer的长度 size_t bufferSize = aclGetDataBufferSizeV2(dataBuffer); // 将指定内存从device拷贝到host的内存上,此时buffer内存即为模型多个推理输出中的指定索引输出 void* buffer = new uint8_t[bufferSize]; aclError aclRet = aclrtMemcpy(buffer, bufferSize, dataBufferDev, bufferSize, ACL_MEMCPY_DEVICE_TO_HOST); // **析构流程** 这里的析构是说,在每次执行完之后,都要释放掉input重新创建,因为input部分不可复用 if (input_ != nullptr) { for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(input_); ++i) { aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(input_, i); aclDestroyDataBuffer(dataBuffer); } aclmdlDestroyDataset(input_); input_ = nullptr; } // 输出部分的析构可以放在类的析构时再做 if (output_ != nullptr) { // 此处应该写入日志 for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output_); ++i) { aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, i); void* data = aclGetDataBufferAddr(dataBuffer); (void)aclrtFree(data); (void)aclDestroyDataBuffer(dataBuffer); } (void)aclmdlDestroyDataset(output_); output_ = nullptr; }