AscendCL快速入门——模型推理篇(上)

AscendCL快速入门——模型推理篇(上) 四叶草~ 2023-04-12 19:11:06 1412

上一篇 AscendCL快速入门——内存管理篇

一、概述

本文介绍了AscendCL模型推理相关知识,介绍了AscendCL接口加载离线模型,为离线模型准备数据结构以及调用离线模型进行推理的过程。简单来说,曻腾的AscendCL的推理工程可以问为三步。

  • 把经过ATC转化过的模型.om文件加载到内存。
  • 为模型准备输入输出。
  • 让模型在设备端执行推理。

二、模型推理的接口调用和代码示例

1. 将模型加载到内存

AscendCL推理所使用的的模型是昇腾CANN平台专用的离线模型,既然要调用模型进行推理,首先当然是要把模型加载进来,最简单的场景就是从磁盘加载一个离线模型文件进内存,接口如下:

aclError aclmdlLoadFromFile(const char *modelPath, uint32_t *modelId);

参数表中的modelPath是入参,指的是离线模型文件在磁盘上的路径;而modelId则是出参,模型加载进内存后,AscendCL会生成一个modelId,后续在分析、使用模型的时候会用到,每次加载模型生成的modelId都是不一样的, 在一个进程空间内,modelId会保持唯一。

开始编写代码前,要加上.h或者.cpp文件中包含AscendCL的头文件:

   #include "acl/acl.h"
#pragma add_include_path("/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/
acllib/include/")
#pragma add_library_path("/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/
acllib/lib64/")
#pragma cling load("libascendcl.so")

    #define INFO_LOG(fmt, args...) fprintf(stdout, "[INFO] " fmt "\n", ##args)
    #define WARN_LOG(fmt, args...) fprintf(stdout, "[WARN] " fmt "\n", ##args)
    #define ERROR_LOG(fmt, args...) fprintf(stdout, "[ERROR] " fmt "\n", ##args)

    #include <iostream>
    #include "acl/acl.h"
    #include <stdio.h>
    #include <fstream>
    #include <cstring>
    #include <sys/stat.h>
    #include <map>
    #include <sstream>
    using namespace std;

阅读下段代码,理解接口调用逻辑

    aclError test1()
    {
     INFO_LOG("AclmdlLoadFromFile: start.");
     const char* aclConfigPath = "";
     aclError ret = aclInit(aclConfigPath);
     ret = aclrtSetDevice(0);
     const char *modelPath = "./googlenet.om";
     uint32_t modelId;
     ret = aclmdlLoadFromFile(modelPath, &modelId);
     INFO_LOG("ModelId = %d.", modelId);
     aclmdlUnload(modelId);
     aclFinalize();
     INFO_LOG("AclmdlLoadFromFile: end.");
     return ret;
    }
    test1();

上边说到的从磁盘加载模型是最简单的场景,但不代表模型只能从磁盘加载进来。某些场景下,模型本身已经在内存中了,此时为了将其加载进AscendCL运行时环境,总不能把这部分模型先写进磁盘,再调aclmdlLoadFromFile从磁盘加载一次吧?所以,这里我们需要一个能从内存加载模型的接口:

aclError aclmdlLoadFromMem(const void* model, size_t modelSize, uint32_t* modelId)

  • model:模型在内存中的地址,当应用运行在Host时,此处需申请Host上的内存;当应用运行在Device时,此处需申请Device上的内存。
  • modelSize:内存中的模型数据长度。
  • modelId:还是模型加载后的唯一标识。

阅读下面代码,体会接口的调用方式

    aclError test2()
    {
     INFO_LOG("AclmdlLoadFromMemory: start.");
     aclError ret = aclInit(nullptr);
     int32_t deviceId_ = 0;
     ret = aclrtSetDevice(deviceId_);
     std::string modelPath = "./googlenet.om";
     uint32_t modelSize = 0;
     void *modelHostData = nullptr;
     std::ifstream modelFile(modelPath, std::ifstream::binary);
     modelFile.seekg(0, modelFile.end);
     modelSize = modelFile.tellg();
     modelFile.seekg(0, modelFile.beg);
     ret = aclrtMallocHost(&modelHostData, modelSize);
     modelFile.read((char*)modelHostData, modelSize);
     modelFile.close();

     uint32_t modelId = 0;
     ret = aclmdlLoadFromMem(modelHostData, modelSize, &modelId);
     INFO_LOG("Model Id = %d.", modelId);
     aclmdlUnload(modelId);
     aclrtFreeHost(modelHostData);
     ret = aclrtResetDevice(deviceId_);
     aclFinalize();
     INFO_LOG("AclmdlLoadFromMemory: end.");
     return ret;
    }
    test2();

模型加载进来之后,在内存中的哪里呢?换句话说,模型加载进来之后,存放模型的内存的指针我们能不能拿到?很遗憾,用上边这两个接口,我们是拿不到其指针的。其实也好理解,对于很多开发者来讲,模型加载进来之后,只要返回给开发者一个modelId,开发者能调用模型进行推理就够了,模型在内存中存放在哪里并不重要。但是对于一些多模型推理的应用来讲,这个问题就要多思考一步了。首先,设备总内存是有限的,每个模型加载进来都要占用一部分内存。对于多模型串行推理的应用来讲,推理之前一股脑地将所有模型都加载进内存,可能导致内存不足,或者加载数据的时候发现内存不足。这种场景下,我们通常会考虑这样做:

  • 加载模型a
  • 调用模型a进行推理,得到结果a1
  • 卸载模型a
  • 加载模型b
  • 把a1送进模型b进行推理,得到结果b1
  • 卸载模型b
  • 加载模型c

而每次模型的加载和卸载都涉及内存的申请和释放(用前边两个接口的话,保存模型的内存是由系统托管的,加载和释放都是在调用模型加载接口的时候自动实施的),频繁的内存申请和释放是很浪费时间的事情,这种时候,就很自然而然的想到要实施内存池方案了。简单来讲,就是每次加载模型的时候,从内存池中捞一段内存来存储模型;卸载模型之后,这部分内存要还回内存池。但要想实现这个方案,首先我们得能拿到模型加载进来之后的内存指针呀。于是我们有了下边这个接口:

aclError aclmdlLoadFromFileWithMem(const char *modelPath,uint32_t *modelId, void *workPtr, size_t workSize, void *weightPtr, size_t weightSize);

workPtr/workSize指的是“工作内存”的指针和大小;weightPtr/weightSize指的是“权值内存”的指针和大小。一个模型加载进来之后,AscendCL是将其分为两部分来保存的,一部分叫“工作内存”,指的是模型运行过程中所占用的内存(比如计算图,不包含权值的部分);另一部分叫“权值内存”,顾名思义,专门保存模型的权值数据。模型加载进来之后,是要提供给NPU使用的,那么加载进系统之后,保存的位置自然是Device侧,所以这里所说的“工作内存”和“权值内存”肯定都得是Device侧内存。

用aclmdlLoadFromFileWithMem接口加载模型,模型加载进来之后的内存地址要我们自己指定,要求有二,一是这部分内存得在Device侧,二是这部分内存得在调用aclmdlLoadFromFileWithMem接口之前就申请好,究竟申请多大的内存,能够给工作内存和权值内存使用呢?来看一个上述接口的配套接口:

aclError aclmdlQuerySize(const char *fileName, size_t *workSize, size_t *weightSize);

这个接口用于查询一个磁盘上的模型文件,如果要加载进系统,需要多大的工作内存和权值内存。有了这个接口,查出工作内存和权值内存大小,我们就能够提前申请Device内存供模型加载。阅读下面代码,理解接口调用流程。

    aclError test3()
    {
     INFO_LOG("AclmdlLoadFromFileWithMem: start.");
     aclError ret = aclInit(nullptr);
     int32_t deviceId_ = 0;
     ret = aclrtSetDevice(deviceId_);
     const char *modelPath = "./googlenet.om";
     size_t workSize = 0;
     void* workPtr = nullptr;
     size_t weightSize = 0;
     void* weightPtr = nullptr;
     ret = aclmdlQuerySize(modelPath, &workSize, &weightSize);
     ret = aclrtMalloc(&workPtr, workSize, ACL_MEM_MALLOC_HUGE_FIRST);
     ret = aclrtMalloc(&weightPtr, weightSize, ACL_MEM_MALLOC_HUGE_FIRST);
     uint32_t modelId = 0;
     ret = aclmdlLoadFromFileWithMem(modelPath, &modelId, workPtr, workSize, weightPtr, weightSize);
     INFO_LOG("ModelId = %d.", modelId);
     ret = aclmdlUnload(modelId);
     ret = aclrtFree(workPtr);
     ret = aclrtFree(weightPtr);
     ret = aclrtResetDevice(deviceId_);
     aclFinalize();
     INFO_LOG("AclmdlLoadFromFileWithMem: end.");
     return ret;
    }
    test3();

既然有aclmdlLoadFromFileWithMem,自然就有aclmdlLoadFromMemWithMem:

aclError aclmdlLoadFromMemWithMem(const void *model, size_t modelSize, uint32_t *modelId,void *workPtr,size_t workSize,void *weightPtr,size_t weightSize);

2. 给模型准备输入输出

将在下一篇中介绍

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
四叶草~
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区