找回密码
 注册
关于网站域名变更的通知
查看: 190|回复: 1
打印 上一主题 下一主题

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

[复制链接]

该用户从未签到

跳转到指定楼层
1#
 楼主| 发表于 2023-8-24 13:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
本帖最后由 Heaven_1 于 2023-8-25 10:51 编辑
1 e( W# l# [6 F' F4 a8 `  Q, F) L' c5 \& `

* ]! F2 m* e' @' k8 C, X5 w
本文介绍了AscendCL模型推理相关知识,介绍了AscendCL接口加载离线模型,为离线模型准备数据结构以及调用离线模型进行推理的过程。简单来说,曻腾的AscendCL的推理工程可以问为三步。
  • 把经过ATC转化过的模型.om文件加载到内存。
  • 为模型准备输入输出。
  • 让模型在设备端执行推理。, s7 u3 v0 P# j5 _
二、模型推理的接口调用和代码示例1. 将模型加载到内存
AscendCL推理所使用的的模型是昇腾CANN平台专用的离线模型,既然要调用模型进行推理,首先当然是要把模型加载进来,最简单的场景就是从磁盘加载一个离线模型文件进内存,接口如下:
aclError aclmdlLoadFromFile(const char *modelPath, uint32_t *modelId);
参数表中的modelPath是入参,指的是离线模型文件在磁盘上的路径;而modelId则是出参,模型加载进内存后,AscendCL会生成一个modelId,后续在分析、使用模型的时候会用到,每次加载模型生成的modelId都是不一样的, 在一个进程空间内,modelId会保持唯一。
开始编写代码前,要加上.h或者.cpp文件中包含AscendCL的头文件:
  1.    #include "acl/acl.h"
    % Z' ^4 o1 m# o5 Y5 F, M$ n' E
  2. #pragma add_include_path("/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/
    , B- C; v" P( w# ]
  3. acllib/include/")2 M$ J  J. L) `8 w0 X0 P' F
  4. #pragma add_library_path("/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux/) u1 D) m, X* o+ R  {
  5. acllib/lib64/")/ I4 G+ A, e3 h& ]6 c8 ]1 m
  6. #pragma cling load("libascendcl.so")
    ; U2 N8 D1 f0 F8 L, I1 g: R
  7. # }1 [5 ?7 y6 E0 \2 A8 a! J
  8.     #define INFO_LOG(fmt, args...) fprintf(stdout, "[INFO] " fmt "\n", ##args)
    ! W5 _# j* |8 Z0 L2 o* j/ c
  9.     #define WARN_LOG(fmt, args...) fprintf(stdout, "[WARN] " fmt "\n", ##args)1 u( Z  I. T" d& }) W
  10.     #define ERROR_LOG(fmt, args...) fprintf(stdout, "[ERROR] " fmt "\n", ##args)- K7 }6 {& X# n  m- b! V7 W# n

  11. : I8 R) r9 j& Q: D! A
  12.     #include <iostream>
    5 I4 I" Y; d3 d. j4 ]
  13.     #include "acl/acl.h"
    8 J. f: L& h3 x( p
  14.     #include <stdio.h>
    / ]7 {! i4 b; S
  15.     #include <fstream>% f; o4 S6 Y( y: y! ~
  16.     #include <cstring>
    3 v$ B, p- Q! D5 K9 Z7 a# u7 s
  17.     #include <sys/stat.h>
    5 ^# z7 Q- S0 k! X0 a
  18.     #include <map>
    ' P: D. |. h' P% Z- c
  19.     #include <sstream>
    ) Q: \, z7 ^) I
  20.     using namespace std;
复制代码

- k: J$ w6 e% B* f
阅读下段代码,理解接口调用逻辑
) d* ^+ x) J$ S0 k: |5 _

0 ^- s2 f& _- w% j  ]  Z8 E! B+ W

) Z- N; {4 v$ G! j0 x
  1.     aclError test1()5 i  \& q# [* W3 `7 q
  2.     {
    1 L; W: X* J/ L- N
  3.      INFO_LOG("AclmdlLoadFromFile: start.");
    : A0 C  D* U8 d/ H
  4.      const char* aclConfigPath = "";( B; U, C7 n5 R6 L
  5.      aclError ret = aclInit(aclConfigPath);6 j1 |5 G3 k1 q  K5 Q( X% T+ N
  6.      ret = aclrtSetDevice(0);* b) Y  [, T- H) ~4 b0 Q7 x7 }
  7.      const char *modelPath = "./googlenet.om";3 C- D2 Z# W: \: o+ q
  8.      uint32_t modelId;
    + T, i0 f  x. [% Y0 H  {( Q8 Y
  9.      ret = aclmdlLoadFromFile(modelPath, &modelId);
    : A6 l# j# ]1 j8 q4 T
  10.      INFO_LOG("ModelId = %d.", modelId);
    0 m/ }) k( B, B8 a! w, ^9 m
  11.      aclmdlUnload(modelId);, L: i) p4 V0 W8 ?7 @
  12.      aclFinalize();
    5 t/ H7 W$ [  t3 j2 S
  13.      INFO_LOG("AclmdlLoadFromFile: end.");
    9 o4 s. V# _# S; J# l9 J8 T) \
  14.      return ret;
      S% s3 C" a( q' O; @" _
  15.     }
    0 B4 ^6 V( D* j' r( v! Y& `! b  g
  16.     test1();
复制代码
' c4 s: p6 R, I5 g: \
上边说到的从磁盘加载模型是最简单的场景,但不代表模型只能从磁盘加载进来。某些场景下,模型本身已经在内存中了,此时为了将其加载进AscendCL运行时环境,总不能把这部分模型先写进磁盘,再调aclmdlLoadFromFile从磁盘加载一次吧?所以,这里我们需要一个能从内存加载模型的接口:
aclError aclmdlLoadFromMem(const void* model, size_t modelSize, uint32_t* modelId)
  • model:模型在内存中的地址,当应用运行在Host时,此处需申请Host上的内存;当应用运行在Device时,此处需申请Device上的内存。
  • modelSize:内存中的模型数据长度。
  • modelId:还是模型加载后的唯一标识。
    1 @: N9 o3 C# C+ W3 B
阅读下面代码,体会接口的调用方式
  1.     aclError test2()  b  m. t3 h: ~" o" Y5 E, S
  2.     {3 s9 k( t3 w  N5 T& C5 t" |  F
  3.      INFO_LOG("AclmdlLoadFromMemory: start.");
    / M' A# F) v% R+ |
  4.      aclError ret = aclInit(nullptr);
    - j. |9 Y: x, l3 p
  5.      int32_t deviceId_ = 0;9 E9 K! i6 I' x5 m
  6.      ret = aclrtSetDevice(deviceId_);
    5 x4 m6 `& B3 u: n3 Y% k
  7.      std::string modelPath = "./googlenet.om";& L' ]! t: b0 t- q) Z
  8.      uint32_t modelSize = 0;
    - ]6 P% O. q' J! }& ]4 ~0 d1 y
  9.      void *modelHostData = nullptr;
    ' r( e* m9 ~* @0 [
  10.      std::ifstream modelFile(modelPath, std::ifstream::binary);0 ~% V3 g5 M0 }
  11.      modelFile.seekg(0, modelFile.end);
    3 [4 g( J3 |9 y5 B7 l
  12.      modelSize = modelFile.tellg();
    8 ?5 x1 \; ?+ I: l$ R% f2 _  C- v1 p
  13.      modelFile.seekg(0, modelFile.beg);
    6 D: `" P. H2 l% Q
  14.      ret = aclrtMallocHost(&modelHostData, modelSize);% Z0 y5 I8 Z+ v( {' ?
  15.      modelFile.read((char*)modelHostData, modelSize);) u6 |, c6 ?& j- [" [
  16.      modelFile.close();* w( {6 s8 e5 s- N4 I, v6 D

  17. ! x4 D# O- v5 v
  18.      uint32_t modelId = 0;$ L  e/ Y4 }4 v/ n, \3 L/ R+ Y
  19.      ret = aclmdlLoadFromMem(modelHostData, modelSize, &modelId);3 n) _2 z) T4 M# j
  20.      INFO_LOG("Model Id = %d.", modelId);4 i3 V5 u! U0 R* M* K
  21.      aclmdlUnload(modelId);
    " F% ?* |+ w4 F2 a7 O, k: M
  22.      aclrtFreeHost(modelHostData);& k7 X7 D+ P6 d
  23.      ret = aclrtResetDevice(deviceId_);! o1 ~0 t" [. t5 ^" G
  24.      aclFinalize();+ h; a9 y1 y) R( |
  25.      INFO_LOG("AclmdlLoadFromMemory: end.");
    3 S+ z3 s% ^+ W. O4 J
  26.      return ret;
    $ \, [6 a* |8 s
  27.     }
    8 b  G& J$ g6 X5 ^/ V
  28.     test2();
复制代码
) _) C! \% B# K# b! l- {
模型加载进来之后,在内存中的哪里呢?换句话说,模型加载进来之后,存放模型的内存的指针我们能不能拿到?很遗憾,用上边这两个接口,我们是拿不到其指针的。其实也好理解,对于很多开发者来讲,模型加载进来之后,只要返回给开发者一个modelId,开发者能调用模型进行推理就够了,模型在内存中存放在哪里并不重要。但是对于一些多模型推理的应用来讲,这个问题就要多思考一步了。首先,设备总内存是有限的,每个模型加载进来都要占用一部分内存。对于多模型串行推理的应用来讲,推理之前一股脑地将所有模型都加载进内存,可能导致内存不足,或者加载数据的时候发现内存不足。这种场景下,我们通常会考虑这样做:
  • 加载模型a
  • 调用模型a进行推理,得到结果a1
  • 卸载模型a
  • 加载模型b
  • 把a1送进模型b进行推理,得到结果b1
  • 卸载模型b
  • 加载模型c
    4 s9 z+ e, f  G+ o  p
而每次模型的加载和卸载都涉及内存的申请和释放(用前边两个接口的话,保存模型的内存是由系统托管的,加载和释放都是在调用模型加载接口的时候自动实施的),频繁的内存申请和释放是很浪费时间的事情,这种时候,就很自然而然的想到要实施内存池方案了。简单来讲,就是每次加载模型的时候,从内存池中捞一段内存来存储模型;卸载模型之后,这部分内存要还回内存池。但要想实现这个方案,首先我们得能拿到模型加载进来之后的内存指针呀。于是我们有了下边这个接口:
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内存供模型加载。阅读下面代码,理解接口调用流程。
  1.     aclError test3()
      M  x- R$ \3 W
  2.     {
    # j) b8 i. S9 ?
  3.      INFO_LOG("AclmdlLoadFromFileWithMem: start.");
    , A* R* A8 `6 T' m* c" P
  4.      aclError ret = aclInit(nullptr);
    0 d1 i9 v. t, T7 G
  5.      int32_t deviceId_ = 0;  ~$ ?7 W0 m6 p
  6.      ret = aclrtSetDevice(deviceId_);' v2 b5 _8 L) a  @
  7.      const char *modelPath = "./googlenet.om";2 P2 I% g$ B2 _9 L
  8.      size_t workSize = 0;- T" H3 R3 U' A8 G7 v
  9.      void* workPtr = nullptr;
    " E% F+ ]3 N; d% t$ W8 D
  10.      size_t weightSize = 0;
    . F' j9 w- E' O2 P2 [8 i
  11.      void* weightPtr = nullptr;
    1 H( _( R: H) t: J1 G1 b# n
  12.      ret = aclmdlQuerySize(modelPath, &workSize, &weightSize);2 w7 P7 t1 ~$ L# A
  13.      ret = aclrtMalloc(&workPtr, workSize, ACL_MEM_MALLOC_HUGE_FIRST);
    7 ?0 r! P  O0 M- v( E
  14.      ret = aclrtMalloc(&weightPtr, weightSize, ACL_MEM_MALLOC_HUGE_FIRST);3 X, K- P- c  V! `
  15.      uint32_t modelId = 0;. s: Q! p- J3 d3 }! Q  j3 p' @, j
  16.      ret = aclmdlLoadFromFileWithMem(modelPath, &modelId, workPtr, workSize, weightPtr, weightSize);
    " V  h) s3 U8 C; D# g* x
  17.      INFO_LOG("ModelId = %d.", modelId);
    / l' G* r! c' U
  18.      ret = aclmdlUnload(modelId);
    ( k' p: n6 j5 T; H+ D
  19.      ret = aclrtFree(workPtr);3 f) a+ Q$ P% I. i
  20.      ret = aclrtFree(weightPtr);; ^) H5 Z8 ^5 F7 i  @6 U/ H
  21.      ret = aclrtResetDevice(deviceId_);6 c$ g7 K- k+ \9 F+ k
  22.      aclFinalize();
      S  e5 P1 e. f) \
  23.      INFO_LOG("AclmdlLoadFromFileWithMem: end.");" i. V9 x+ w2 v: n+ W
  24.      return ret;
    + `! h! q& m& S% w" \; |; h% Y
  25.     }" g2 e; T5 z) {1 |* J
  26.     test3();
复制代码
& x' o# V4 V2 Q$ g& m% N
既然有aclmdlLoadFromFileWithMem,自然就有aclmdlLoadFromMemWithMem:
aclError aclmdlLoadFromMemWithMem(const void *model, size_t modelSize, uint32_t *modelId,void *workPtr,size_t workSize,void *weightPtr,size_t weightSize);
2. 给模型准备输入输出1 f7 z% h9 _' A) d
+ |2 c8 o+ x% `2 g9 k
& {4 \- k. {: U+ u, U# J% ?
9 n* F" r4 ?% p! [; @( M- E
4 B0 d8 Z" d/ T. U9 P7 ^% b, X: h  z

2 V; A$ M# v' \+ @; j: O

该用户从未签到

2#
发表于 2023-8-25 10:52 | 只看该作者
有完整的学习课程嘛
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-7-27 16:37 , Processed in 0.125000 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表