首页 > 技术知识 > 正文

上一篇 AscendCL快速入门——运行资源管理篇(下)

主要介绍了关于AscendCL运行资源管理相关内容

本篇将介绍内存管理相关操作

一、概述

本文介绍了AscendCL内存管理的相关知识,帮助掌握AscendCL中“host”和”device”的概念与使用方法,能够在两侧分别申请和释放内存,并在两侧之间进行内存拷贝和数据传输。

我们用一般计算机进行举例,CPU+内存所在的一侧,或者说进程启动的一侧,收集数据的一侧,我们称之为”host”侧,GPU+显存所在的一侧,进行专用计算,使用数据,我们称之为”Decive“。

在华为设备中,将GPU换为NPU,将显存换为Device内存,CPU+内存是Host侧,NPU则是Device侧,数据在Host收集,并发送到Device侧供NPU进行推理,并将结果从Device侧发回Host侧进行后处理、使用等操作。但是,有些华为设备,进程就是在NPU上进行,数据也是在Device内存直接加载的,此时没有Host侧,也没有Host->Device的数据传输过程,这种情况下,内存管理简单,无需考虑Host侧的事情。

二、接口API与代码展示

Host侧内存管理相关接口方式比较简单,其中aclrtMallocHost是内存申请接口,aclrtFreeHost是其对应的释放接口aclError aclrtMallocHost(void **hostPtr, size_t size); aclError aclrtFreeHost(void *hostPtr);

这里要注意:aclrtMallocHost申请出来的内存必须由aclrtFreeHost来释 放,而 aclrtFreeHost只能释放由aclrtMallocHost申请出来的内存。起来有点拗口,实际就是说aclrtMallocHost和aclrtFreeHost这两个接口是强对应的,不要用于别的内存。

阅读下段代码,理解内存管理接口的使用。

#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> INFO_LOG(“AscendCL Hello World.”); // AscendCL init const char *aclConfigPath = “”; aclError ret = aclInit(aclConfigPath); if ((ret != ACL_ERROR_NONE) && (ret != ACL_ERROR_REPEAT_INITIALIZE)) {  ERROR_LOG(“AscendCL init failed”); } INFO_LOG(“AscendCL init success.”); ret = aclrtSetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } void *hostInput = nullptr; int64_t size_input = 256; ret = aclrtMallocHost(&hostInput, size_input); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL HostMem Malloc failed %d .”, ret); } else {  INFO_LOG(“AscendCL HostMem Malloc success .”); } if (hostInput != nullptr) {  ret = aclrtFreeHost(hostInput);  if (ret != ACL_SUCCESS) {   ERROR_LOG(“AscendCL HostMem Free failed %d .”, ret);  } else {   INFO_LOG(“AscendCL HostMem Free success .”);  } } ret = aclrtResetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } ret = aclFinalize(); if ((ret != ACL_ERROR_NONE)&& (ret != ACL_ERROR_REPEAT_FINALIZE)) {  ERROR_LOG(“Finalize AscendCL failed.”); } INFO_LOG(“End to finalize AscendCL.”); return 0;
<
Device侧内存管理aclError aclrtMalloc(void **devPtr, size_t size, aclrtMemMallocPolicy policy); aclError aclrtFree(void *devPtr);

申请内存接口又多个参数:policy,指明申请内存的策略,当前一共有三种策略可选ACL_MEM_MALLOC_HUGE_FIRST:当申请的内存小于等于1M时,即使使用该内存分配规则, 也是申请普通页的内存。当申请的内存大于1M时,优先申请大页内存,如果大页内存不够,则使用普通页的内存。

ACL_MEM_MALLOC_HUGE_ONLY:仅申请大页,如果大页内存 不够,则返回错误。

ACL_MEM_MALLOC_NORMAL_ONLY:仅申请普通页。

aclrtMalloc和aclrtFree要成对出现。用aclrtMalloc申请出来的内存也是对齐过的。

阅读如下代码,理解Device侧内存管理使用。

INFO_LOG(“AscendCL Hello World.”); // AscendCL init ret = aclInit(aclConfigPath); if ((ret != ACL_ERROR_NONE) && (ret != ACL_ERROR_REPEAT_INITIALIZE)) { ERROR_LOG(“AscendCL init failed.”); } else { INFO_LOG(“AscendCL init success.”); } ret = aclrtSetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } INFO_LOG(“Set device 0 success.”); void *devInput = nullptr; size_input = 256; ret = aclrtMalloc(&devInput, size_input, ACL_MEM_MALLOC_HUGE_FIRST); if (ret != ACL_SUCCESS) { ERROR_LOG(“AscendCL DeviceMem Malloc failed %d.”, ret); } else{ INFO_LOG(“AscendCL DeviceMem Malloc success.”); } if (hostInput != nullptr) { ret = aclrtFree(devInput); if (ret != ACL_SUCCESS) {   ERROR_LOG(“AscendCL DeviceMem Free failed.”);  } else{  INFO_LOG(“AscendCL DeviceMem Free success.”);  } } ret = aclrtResetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } ret = aclFinalize(); if ((ret != ACL_ERROR_NONE)&& (ret != ACL_ERROR_REPEAT_FINALIZE)) { ERROR_LOG(“Finalize AscendCL failed.”); } INFO_LOG(“End to finalize AscendCL.”); return 0;
<
内存拷贝

两侧的内存都申请好了,下面该是对内存的初始化以及在Host与Device之 间拷贝 数据了。刚申请出来的内存,里边的数据是随机的,有时我们想要对其进行一波统一的 初始化,此时可以使用这个接口:

aclError aclrtMemset(void *devPtr, size_t maxCount, int32_t value, size_t count);

devPtr:内存的起始地址

maxCount:内存的最大长度,单位byte

value:设置的值需要设置为指定值的内存长度,单位Byte

接下来是内存拷贝,而拷贝这个动作又分“源地址”和“目的地址”,所以在 AscendCL的内存拷贝动作中,共有4种拷贝方向。内存拷贝的函数原型如下:

aclError aclrtMemcpy(void *dst, size_t destMax, const void *src, size_t count,aclrtMemcpyKind kind);

Dst:目的地址

destMax:目的内存地址的最大内存长度,单位Byte

src:源地址

count:内存复制的长度,单位Byte

kind:指向内存复制方向

阅读下面代码来理解本接口的使用

INFO_LOG(“AscendCL Hello World.”); // AscendCL init ret = aclInit(aclConfigPath); if ((ret != ACL_ERROR_NONE) && (ret != ACL_ERROR_REPEAT_INITIALIZE)) {  ERROR_LOG(“AscendCL init failed.”); } INFO_LOG(“AscendCL init success.”); ret = aclrtSetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } hostInput = nullptr; devInput = nullptr; size_input = 256; ret = aclrtMallocHost(&hostInput, size_input); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL HostMem Malloc failed.”); } else{  INFO_LOG(“AscendCL HostMem Malloc success.”); } ret = aclrtMalloc(&devInput, size_input, ACL_MEM_MALLOC_HUGE_FIRST); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL DeviceMem Malloc failed %d.”,ret ); } else{  INFO_LOG(“AscendCL DeviceMem Malloc success .”); } ret = aclrtMemset(hostInput, size_input, 1, size_input); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL HostMem Memset failed %d.”,ret ); } else{  INFO_LOG(“AscendCL HostMem Memset success.”); } ret = aclrtMemset(devInput, size_input, 0, size_input); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL DeviceMem Memset failed %d.”,ret ); } else{  INFO_LOG(“AscendCL DeviceMem Memset success.”); } aclrtMemcpy(devInput, size_input, hostInput, size_input, ACL_MEMCPY_HOST_TO_DEVICE); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL Memcopy Host to Device failed %d.”,ret ); } else{  INFO_LOG(“AscendCL Memcopy Host to Device success.”); } if (hostInput != nullptr) {  ret = aclrtFreeHost(hostInput);  if (ret != ACL_SUCCESS) {   ERROR_LOG(“AscendCL HostMem Free failed %d.”,ret );  }  else{   INFO_LOG(“AscendCL HostMem Free success.”);  } } if (devInput != nullptr) {  ret = aclrtFree(devInput);  if (ret != ACL_SUCCESS) {   ERROR_LOG(“AscendCL DeviceMem Free failed %d.”,ret );  }  else{   INFO_LOG(“AscendCL DeviceMem Free success.”);  } } ret = aclrtResetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return 0; } ret = aclFinalize(); if ((ret != ACL_ERROR_NONE)&& (ret != ACL_ERROR_REPEAT_FINALIZE)) {  ERROR_LOG(“Finalize AscendCL failed.”); } INFO_LOG(“End to finalize AscendCL.”); return 0;
<
如果要获取当前设备有多少内存,以及多少可用内存,可以选用这个接口:

aclError aclrtGetMemInfo(aclrtMemAttr attr, size_t *free, size_t *total)

其中的attr参数指的是内存的类型,主要有以下几种:

ACL_DDR_MEM, //DDR内存,DDR上所有大页内存+普通内存

ACL_DDR_MEM_HUGE, //DDR大页内存

ACL_DDR_MEM_NORMAL, //DDR普通内存

选择内存类型的时候要根据当前使用的设备形态来选择,阅读以下代码

INFO_LOG(“AscendCL Hello World.”); // AscendCL init ret = aclInit(aclConfigPath); if ((ret != ACL_ERROR_NONE) && (ret != ACL_ERROR_REPEAT_INITIALIZE)) {  ERROR_LOG(“AscendCL init failed.”);  return ret; } INFO_LOG(“AscendCL init success.”);   ret = aclrtSetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return ret; } size_t free_mem = 0; size_t total_mem = 0; ret = aclrtGetMemInfo(ACL_DDR_MEM, &free_mem, &total_mem); if (ret != ACL_SUCCESS) {  ERROR_LOG(“AscendCL GetMemInfo failed %d.”,ret);  return ret; } INFO_LOG(“available memory: %zu”, free_mem); INFO_LOG(“total memory: %zu”, total_mem); ret = aclrtResetDevice(0); if (ret != ACL_ERROR_NONE) {  ERROR_LOG(“AclrtSetDevice failed %d.”,ret);  return ret; } ret = aclFinalize(); if ((ret != ACL_ERROR_NONE)&& (ret != ACL_ERROR_REPEAT_FINALIZE)) {  ERROR_LOG(“Finalize AscendCL failed.”);  return ret; } INFO_LOG(“End to finalize AscendCL.”); return 0;
<
运行模式判断,部署环境下,为了明确在哪种模式下运行,提供了以下接口

aclError aclrtGetRunMode(aclrtRunMode *runMode);

runMode参数是接口调用后输出的一个枚举值,有两种可能:

ACL_DEVICE, //昇腾AI软件栈运行在Device的Control CPU或板端环境上

ACL_HOST, //昇腾AI软件栈运行在Host CPU上。

下篇将介绍 AscendCL入门 —— 模型推理篇

猜你喜欢