华为CANN训练营笔记——应用开发全流程 [2](with 代码版)

10 篇文章 4 订阅

1.3 ACL运行资源管理

包含物理资源和逻辑资源。

1.3.1 运行资源管理概述

按顺序申请:Device -> Context -> Stream,然后根据实际需求调用aclrtGetRunMode接口获取软件栈的运行模式(当同一个应用既支持在Host运行,也支持在Device运行时,在编程时需要就需要根据运行模式来判断后续的接口调用逻辑,这时需要提前获取运行模式。)。

在这里插入图片描述

  • 显示指定Device:调用aclrtSetDevice接口
    • 显示创建Context和Stream:调用aclrtCreateContext和aclrtCreateStream
    • 不显示创建二者:系统会默认的使用Context和Stream。二者是在调用aclreSetDevice是隐式创建的,默认Context和默认Stream作为接口入参时,传入NULL即可。
  • 不显示指定Device:调用aclrtCreateContext和alcrtCreateStream显示创建Context与Stream。系统在显示创建Context时,会调用aclrtSetDevice接口指定运行的Device,Device ID通过aclrtCreateContext传入

aclrtGetRunMode判断进程是在Device还是HOST上。如果HOST上,需要进行HOST和Device之间的数据传输;进程部署在Device上则不需要。

Device是物理硬件;Context是逻辑上的资源集合;Stream控制异步进程

一个Context对应一个唯一Device。切换Device时直接setcardcontext。
在这里插入图片描述

接口命名规则

在这里插入图片描述

1.3.2 Device管理

参数带 * (传入指针)表示是输出;不带 * (传入变量)表示是输入

aclError aclrtGetDeviceCount(uint32_t *count):获取当前可用device数量

aclError aclrtGetRunMode(aclrtRunMode *runMode):获取运行模式。runMode是枚举类型,0表示Device的CPU上;1表示HOST的CPU上

aclError aclrtSetDevice(int32_t deviceId):指定运算的Device并隐式创建Context和Stream。

此处会产生一个默认的Stream。

  • 可在进程或线程中指定用于运算的Device,若多次调用aclrtSetDevice接口指定同一个Device,仅需调用一次acrtResetDevice接口复位Device。
  • 在不同进程或线程中可指定同一个Device用于运算。
  • 在某一进程中指定Device,该进程内的多个线程可共用此Device显式创建Context (aclrtCreateContext接口)
  • 多Device场景下,可在进程中通过acIrtSetDevice接口切换到其它Device。

aclError aclrtResetDevice(int32_t deviceld):释放Device资源。释放Event/显式创建的Stream→释放显式创建的Context→>ResetDevice

aclError aclrtGetDevice(int32_t deviceld):获取正在运行的Device ID

Device Demo

ret表示函数的返回错误代码

# include <iostream>
# include "acl/acl.h"
# 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)
using namespace std;

int main(){
	/*
		ACL初始化和销毁步骤不变,中间扩增Device申请与销毁代码
	*/
	INFO_LOG("HELLO WORLD");
	// ACL初始化
	const char *aclConfigPath = "acl.json";
	aclError ret = aclInit(aclConfigPath);
	if (ret != ACL_ERROR_NONE){	// 判断初始化成功
		ERROR_LOG("acl init failed");
	}
	INFO_LOG("acl init success");
	
	
	// 获取有多少Device
	uint32_t count;	
	ret = aclrtGetDeviceCount(&count) ; 
	INFO_LOG( "Device count: %d", count);
	
	// 指定Device
	ret = aclrtSetDevice(0); 
	INFO_LOG( "set device 0 success." );
	
	// 查看当前Device
	int32_t deviceId;
	ret = aclrtGetDevice(&deviceId);
	INFO_LOG( "current deviceID:%d", deviceId);
	
	ret = aclrtSetDevice(1);
	INFO_LOG( "set device 1 success." );
	ret = aclrtGetDevice(&deviceId);
	INFO_LOG( "current deviceID: %d" , deviceId);
	
	// 查看运行模式
	aclrtRunMode runMode;
	ret = aclrtGetRunMode(&runMode);
	INFO_LOG( "current run mode: %d . ", runMode );
	
	// 复位Device
	ret = aclrtResetDevice(0);
	INFO_LOG( "Reset device 0 success." );
	ret = aclrtResetDevice(1);
	INFO_LOG( "Reset device 1 success. " );
	
	ret = aclFinalize();
	// ACL销毁
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("finalize acl failed");
	}
	INFO_LOG("end to finalize acl");
	return 0;
}

同样,需要先编译cmake . -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUE, make
然后cd到目录下,运行touch acl.json -> ./main

上述device_demo是在有两个device时运行的。当device仅有一个时虽然不会报错,但ret=aclrtSetDevice(1)不会执行

1.3.3 Context管理

aclError aclrtCreateContext(aclrtContext *context, int32_t deviceId):显示创建Context,该Context中包含两个Stream,一个默认Stream和一个执行内部同步的Stream。

  • 不调用aclrtCreateContext接口显式创建Context,那系统会使用默认Context,该默认Context是在调用aclrtSetDevice接口时隐式创建的。
  • 若在某一进程内创建多个Context(无Context数量限制),当前线程在同一时刻内只能使用其中一个Context,建议通过aclrtSetCurrentContext接口明确指定当前线程的Context,增加程序的可维护性。

aclError aclrtDestroyContext(aclrtContext context):释放Context资源

aclError aclrtSetCurrentContext(aclrtContext context)指定线程的context。在需要指定context的线程内执行。推荐在某一线程中创建的Context只在该线程内使用

  • 如果在某线程(例如: thread1)中调用aclrtCreateContext接口显式创建一个Context(例如: ctx1),则可以不调用aclrtSetCurrentContext接口指定该线程的Context,系统默认将ctx1作为thread1的Context。(显示创建的Context不需要指定
  • 如果没有调用aclrtCreateContext接口显式创建Context,则系统将默认Context作为线程的Context,此时,不能通过aclrtDestroyContext接口来释放默认Context。(不显示创建的Context不能显示释放
  • 如果多次调用acIrtSetCurrentContext接口设置线程的Context,以最后一次为准

aclError aclrtGetCurrentContext(aclrtContext *context): 获取线程的Context

context Demo
# include <iostream>
# include "acl/acl.h"
# 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)
using namespace std;

int main(){
	/*
		不显示创建device,在调用aclrtCreateContext是默认指定device
	*/
	INFO_LOG("Context Demo");
	// ACL初始化
	const char *aclConfigPath = "acl.json";
	aclError ret = aclInit(aclConfigPath);
	if (ret != ACL_ERROR_NONE){	// 判断初始化成功
		ERROR_LOG("acl init failed");
	}
	INFO_LOG("acl init success");

	int32_t deviceId = 0;
	aclrtContext context1;
	aclrtContext context2;
	ret = aclrtCreateContext(&context1, deviceId);	// 显示创建Context1
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("acl create context1 failed");
	}
	INFO_LOG("create context1 success");

	ret = aclrtCreateContext(&context2, deviceId);	// 显示创建Context2
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("acl create context2 failed");
	}
	INFO_LOG("create context2 success");
	
	aclrtContext context3;
	ret = aclrtGetCurrentContext(&context3); // 获取当前的context
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("acl get current context failed");
	}
	INFO_LOG("acl get current context success");
	
	// 比较 输出当前进程是哪个context
	if(context2 == context3){	// 比较地址
		INFO_LOG("current context is context2");
	}
	else if (context1 == context3){
		INFO_LOG("current context is context1");
	}
	else ERROR_LOG("current context compare failed");

	// 将context设置为context1
	ret = aclrtSetCurrentContext(context1);
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("acl set current failed");
	}
	INFO_LOG("set current context as context1 successed");
	
	 // 查看更换context是否成功
	ret = aclrtGetCurrentContext(&context3);
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("acl get current context failed");
	}
	INFO_LOG("acl get current context success");

	if(context2 == context3){
	INFO_LOG("current context is context2");
	}
	else if (context1 == context3){
		INFO_LOG("current context is context1");
	}
	else ERROR_LOG("current context compare failed");
	
	// 销毁context
	ret = aclrtDestroyContext(context1);
	INFO_LOG("context1 is destroyed");
	ret = aclrtDestroyContext(context2);
	INFO_LOG("context2 is destroyed");
	
	// 销毁acl
	ret = aclFinalize();
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("finalize acl failed");
	}
	INFO_LOG("end to finalize acl");
	return 0;
}

然后编写对应的CMakeLists.txt;执行cmake . -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUEmaketouch acl.json./main

  • result
    在这里插入图片描述

1.3.4 Stream管理

在ACL中,Stream是一个任务队列,通过Stream管理任务并行。即Stream根据发送过来的任务依次执行; 不同Stream中的任务并行执行。一个默认Context下会挂一个默认Stream,如果不显式创建Stream,可使用默认Stream。

aclError aclrtCreateStream(aclrtStream *stream):创建一个Stream。硬件资源最多支持1024个Stream。

每个进程对应一个默认Stream,此默认接口是调用aclrtSetDevice接口指定用于运算的Device时隐式创建的。系统最大支持8个默认Stream

aclError aclrtDestroyStream(aclrtStream stream):销毁stream。销毁前需调用aclrtSynchronizeStream接口确保Stream中任务都已完成

??????????????aclrtSynchronizeStream:视频中没讲,埋坑

Stream Demo
# include <iostream>
# include "acl/acl.h"
// 设置宏
# 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)
using namespace std;

int main(){
	/*
		注意创建与销毁顺序恰好相反
	*/

	INFO_LOG("HELLO WORLD");
	// ACL初始化
	const char *aclConfigPath = "acl.json";
	aclError ret = aclInit(aclConfigPath);
	if (ret != ACL_ERROR_NONE){	// 判断初始化成功
		ERROR_LOG("acl init failed");
	}
	INFO_LOG("acl init success");
	
	// 创建context
	int32_t deviceId = 0;
	aclrtContext context;
	ret = aclrtCreateContext(&context, deviceId);
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("context create failed");
	}
	INFO_LOG("create context success");

	// 创建stream
	aclrtStream stream;
	ret = aclrtCreateStream(&stream);
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("stream create failed");
	}
	INFO_LOG("create stream success");
	
	// 销毁stream
	ret = aclrtDestroyStream(stream);
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("stream destroy failed");
	}
	INFO_LOG("end to stream destroy");
	
	// 销毁context
	ret = aclrtDestroyContext(context);
	INFO_LOG("end to context destroy");

	ret = aclFinalize();
	// ACL销毁
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("finalize acl failed");
	}
	INFO_LOG("end to finalize acl");
	return 0;
}
  • result
    在这里插入图片描述

1.4 内存管理与数据传输

通常加速计算在Device,而模型、数据集等加载在HOST上。

1.4.1 内存管理概述

任何用于参与ACL在Device侧运算的内存,都不能是C/C++原生内存管理接口,而是必须调用ACL相关接口进行管理。 ACL设备管理上,是要区分HOST和Device的(aclrtGetRunMode返回运行模式)。

  • 二者分设:如Atlas300,模型数据等都在HOST加载;然后传输到Device侧运算;运算结果再返回HOST
  • 二者合设:只有Device,模型数据等都在Device上加载

1.4.2 内存管理接口

1. Host侧申请与释放

aclError aclrtMallocHost(void **hostPtr, size_t size):申请Host侧内存。申请的内存不能直接在Device上使用,需要显式拷贝到Device上。这个内存只能通过aclrtFreeHost释放。

aclError aclrtFreeHost(void *hostPtr)

2. Device侧申请与释放

aclError aclrtMalloc(void **devPtr, size_t size, aclrtMemMallocPolicy policy):申请Device侧内存。policy指内存分配规则。
aclError aclrtFree(void *devPtr)

3. 新申请的内存初始化
  • 同步
    aclError aclrtMemset(void *devPtr, size_t maxCount, int32_t value, size_t count):内存赋初值。

  • 异步
    aclError aclrtMemsetAsync(void *devPtr, size_t maxCount, int_32 value, size_t count, aclrtStream stream)异步初始化

4. HOST与Device之间拷贝
  • 同步
    aclError aclrtMemcpy(void *dst, size_t destMax, const void *src, size_t count, aclrtMemcpyKind kind)

    • dst:目的内存地址指针
    • destMax:目的内存地址的最大内存长度
    • src:源内存地址指针
    • count:内存复制长度
    • kind:aclrtMemcpyKind枚举。
      • ACL_MEMCPY_HOST_TO_HOST:Host -> Host
      • ACL_MEMCPY_HOST_TO_DEVICE:Host -> Device
      • ACL_MEMCPY_DEVICE_TO_HOST:Device -> Host
      • ACL_MEMCPY_DEVICE_TO_DEVICE:Device -> Device
  • 异步
    aclError aclrtMemcpyAsync(void *dst, size_t destMax, const void *src, size_t count, aclrtMemcpyKind kind, aclrtStream stream)

    • stream

1.4.3 内存管理Demo

# include <iostream>
# include "acl/acl.h"
// 设置宏
# 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)
using namespace std;

int main(){
	/*
		注意创建与销毁顺序恰好相反
	*/

	INFO_LOG("HELLO WORLD");
	// ACL初始化
	const char *aclConfigPath = "acl.json";
	aclError ret = aclInit(aclConfigPath);
	if (ret != ACL_ERROR_NONE){	// 判断初始化成功
		ERROR_LOG("acl init failed");
	}
	INFO_LOG("acl init success");
	
	ret = aclrtSetDevice(0);
	INFO_LOG("set device 0 success");

	void *hostMemPtr_;
	size_t hostMemSize = 32;
	void *devMemPtr_;
	size_t devMemSize = 32;
	
	// 申请Host内存
	ret = aclrtMallocHost(&hostMemPtr_, hostMemSize);
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("host malloc failed");
	}
	INFO_LOG("acl host malloc success");
	
	// 申请Device内存
	ret = aclrtMalloc(&devMemPtr_, devMemSize, ACL_MEM_MALLOC_HUGE_FIRST); // 注意相比HOST多一个参数
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("device malloc failed");
	}
	INFO_LOG("acl dev malloc success");
	
	// 初始化HOST
	ret = aclrtMemset(hostMemPtr_, 32, 1, 32);	// host内存内容重置为1
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("acl host mem set failed");
	}
	INFO_LOG("acl host mem set success");
	
	// 拷贝 HOST -> Device
	ret = aclrtMemcpy(devMemPtr_, devMemSize, hostMemPtr_, hostMemSize, ACL_MEMCPY_HOST_TO_DEVICE);
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("acl mem copy host2dev failed");
	}
	INFO_LOG("acl mem copy host2dev success");

	// 释放
	ret = aclrtFreeHost(hostMemPtr_);
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("host free failed");
	}
	INFO_LOG("end to free host");
	
	ret = aclrtFree(devMemPtr_);
	if (ret != ACL_ERROR_NONE){	
		ERROR_LOG("device free failed");
	}
	INFO_LOG("end to free dev");

	ret = aclrtResetDevice(0);
	INFO_LOG("Reset device 0 success");

	ret = aclFinalize();
	// ACL销毁
	if (ret != ACL_ERROR_NONE){
		ERROR_LOG("finalize acl failed");
	}
	INFO_LOG("end to finalize acl");
	return 0;
}
  • result
    在这里插入图片描述
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值