数据传输

接口调用流程

数据传输的关键接口调用流程如下:

  1. 申请内存。
    • Host上的内存,可以用C++标准库中的new、malloc接口申请内存,也可以使用AscendCL提供的aclrtMallocHost接口申请内存。
    • Device上的内存,使用AscendCL提供的aclrtMallocaclrtMallocHost接口申请内存。如果涉及媒体数据处理(例如,图片解码、缩放等)时,需使用acldvppMallochi_mpi_dvpp_malloc接口申请内存。
  2. 将数据读入内存。

    由用户自行管理数据读入内存的实现逻辑。

  3. 通过内存复制实现数据传输。
    数据传输可以通过内存复制的方式实现,分为同步内存复制、异步内存复制:

Ascend RC场景下,不涉及Host上的内存申请、Host内的数据传输、Host与Device之间的数据传输。

如果当前版本支持多种运行形态,在这种场景下,若想实现相同的应用程序可支持在多种形态下运行,申请内存的方式不同,会影响数据传输时调用的接口:
  • 若应用程序中区分申请Host内存或Device内存的接口,例如使用C++标准库的接口或aclrtMallocHost接口申请Host内存、使用aclrtMalloc接口申请Device内存时:

    需先调用aclrtGetRunMode接口获取软件栈的运行模式,当查询结果为ACL_HOST,则数据传输时涉及申请Host上的内存;当查询结果为ACL_DEVICE,则数据传输时不涉及申请Host上的内存,仅需申请Device上的内存。该种方式多一些代码逻辑的判断,不需要由用户处理Device上的内存对齐。在Device上运行应用的场景,该种方式少一些内存复制的步骤,性能较好。

  • 若应用程序中不区分申请Host内存或Device内存的接口,统一使用aclrtMallocHost接口(该接口支持申请Host或Device内存),AscendCL内部会根据软件栈的运行模式自行判断运行时申请的是Host内存还是Device内存:

    无需调用aclrtGetRunMode接口获取软件栈的运行模式。该种方式代码逻辑相比前一种简单,但需由用户处理Device上的内存对齐。

Host内的数据传输

当前支持调用aclrtMemcpy接口执行同步Host内的内存复制任务,不支持调用aclrtMemcpyAsync接口执行异步Host内的内存复制功能,若调用aclrtMemcpyAsync接口时选择ACL_MEMCPY_HOST_TO_HOST类型时,由于是异步接口,虽然接口调用成功,下发了内存复制任务,但在调用aclrtSynchronizeStream接口等待该任务执行时会返回失败。

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。

// 1. 申请内存
uint64_t size = 1 * 1024 * 1024;
void* hostPtrA = NULL;
void* hostPtrB = NULL;
aclrtMallocHost(&hostPtrA, size);
aclrtMallocHost(&hostPtrB, size);

// 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现
ReadFile(fileName, hostPtrA, size);

// 3. 内存复制,可以选择同步
// 同步内存复制,hostPtrA表示Host上源内存地址指针,hostPtrB表示Host上目的内存地址指针,size表示内存大小
aclrtMemcpy(hostPtrB, size, hostPtrA, size, ACL_MEMCPY_HOST_TO_HOST);

// 4. 使用完内存中的数据后,需及时释放资源
aclrtFreeHost(hostPtrA);
aclrtFreeHost(hostPtrB);
// ......

从Host到Device的数据传输

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。

从Device到Host的数据传输

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。

一个Device内的数据传输

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。

// 1. 申请内存
uint64_t size = 1 * 1024 * 1024;
void* devPtrA = NULL;
void* devPtrB = NULL;
aclrtMalloc(&devPtrA, size, ACL_MEM_MALLOC_NORMAL_ONLY);
aclrtMalloc(&devPtrB, size, ACL_MEM_MALLOC_NORMAL_ONLY);

// 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现
ReadFile(fileName, devPtrA, size);

// 3. 内存复制,可以选择同步或异步
// 同步内存复制,devPtrA表示Device上源内存地址指针,devPtrB表示Device上目的内存地址指针,size表示内存大小
aclrtMemcpy(devPtrB, size, devPtrA, size, ACL_MEMCPY_DEVICE_TO_DEVICE);
  
// 异步内存复制
aclrtStream stream = NULL;
aclrtCreateStream(&stream);
aclrtMemcpyAsync(devPtrB, size, devPtrA, size, ACL_MEMCPY_DEVICE_TO_DEVICE, stream);
aclrtSynchronizeStream(stream);

// 4. 使用完内存中的数据后,需及时释放资源
aclrtDestroyStream(stream);
aclrtFree(devPtrA);
aclrtFree(devPtrB);

// ......

两个Device间的数据传输

Atlas 200/300/500 推理产品上,不支持该功能。

注意点说明:

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。

int main(int argc, const char *argv[])
{
    // AscendCL初始化
    auto ret = aclInit(NULL);

    int32_t canAccessPeer = 0;
    // 查询Device 0和Device 1之间是否支持内存复制
    ret = aclrtDeviceCanAccessPeer(&canAccessPeer, 0, 1);


    // 1表示支持内存复制
    if (canAccessPeer == 1) {
	// ************************************************************
	// Device 0下的操作
	ret = aclrtSetDevice(0);		
	void *dev0;
	ret = aclrtMalloc(&dev0, 10, ACL_MEM_MALLOC_HUGE_FIRST_P2P);
	ret = aclrtMemset(dev0, 10, 1, 10);
		
	// ************************************************************
	// Device 1下的操作,使能当前Device(Device 1)到指定Device(Device 0)的内存复制,当前Device通过aclrtSetDevice接口设置,指定Device在aclrtDeviceEnablePeerAccess接口的第一个参数指定
	ret = aclrtSetDevice(1);
	ret = aclrtDeviceEnablePeerAccess(0, 0);
	void *dev1;
	ret = aclrtMalloc(&dev1, 10, ACL_MEM_MALLOC_HUGE_FIRST_P2P);
	ret = aclrtMemset(dev1, 10, 0, 10);
		
	// 执行复制,将Device 0上的内存数据复制到Device 1上
	ret = aclrtMemcpy(dev1, 10, dev0, 10, ACL_MEMCPY_DEVICE_TO_DEVICE);
	ret = aclrtResetDevice(1);
        // ************************************************************

	// ************************************************************
	// Device 0下的操作,等Device 1下的复制操作完成后,再调用aclrtResetDevice接口释放Device 0的资源
        ret = aclrtSetDevice(0);
	ret = aclrtResetDevice(0);
	// ************************************************************

	printf("P2P copy success\n");
    } else {
	printf("current device doesn't support p2p feature\n");
    }

    // AscendCL去初始化
    aclFinalize();
    return 0;
}