2.4.cuda驱动API-使用驱动API进行内存分配

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记

本次课程学习精简 CUDA 教程-Driver API 内存分配

课程大纲可见下面的思维导图

在这里插入图片描述

1. 内存分配

在这里我们只做一个基本的演示,更多详细的内容我们会在 Runtime API 上讲

使用 Driver API 进行内存分配案例的示例代码如下:

// CUDA驱动头文件cuda.h
#include <cuda.h>

#include <stdio.h>
#include <string.h>

#define checkDriver(op)  __check_cuda_driver((op), #op, __FILE__, __LINE__)

bool __check_cuda_driver(CUresult code, const char* op, const char* file, int line){
    
    

    if(code != CUresult::CUDA_SUCCESS){
    
        
        const char* err_name = nullptr;    
        const char* err_message = nullptr;  
        cuGetErrorName(code, &err_name);    
        cuGetErrorString(code, &err_message);   
        printf("%s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);   
        return false;
    }
    return true;
}

int main(){
    
    

    // 检查cuda driver的初始化
    checkDriver(cuInit(0));

    // 创建上下文
    CUcontext context = nullptr;
    CUdevice device = 0;
    checkDriver(cuCtxCreate(&context, CU_CTX_SCHED_AUTO, device));
    printf("context = %p\n", context);

    // 输入device prt向设备要一个100 byte的线性内存,并返回地址
    CUdeviceptr device_memory_pointer = 0;
    checkDriver(cuMemAlloc(&device_memory_pointer, 100)); // 注意这是指向device的pointer, 
    printf("device_memory_pointer = %p\n", device_memory_pointer);

    // 输入二级指针向host要一个100 byte的锁页内存,专供设备访问。参考 2.cuMemAllocHost.jpg 讲解视频:https://v.douyin.com/NrYL5KB/
    float* host_page_locked_memory = nullptr;
    checkDriver(cuMemAllocHost((void**)&host_page_locked_memory, 100));
    printf("host_page_locked_memory = %p\n", host_page_locked_memory);

    // 向page-locked memory 里放数据(仍在CPU上),可以让GPU可快速读取
    host_page_locked_memory[0] = 123;
    printf("host_page_locked_memory[0] = %f\n", host_page_locked_memory[0]);
    /* 
        记住这一点
        host page locked memory 声明的时候为float*型,可以直接转换为device ptr,这才可以送给cuda核函数(利用DMA(Direct Memory Access)技术)
        初始化内存的值: cuMemsetD32 ( CUdeviceptr dstDevice, unsigned int  ui, size_t N )
        初始化值必须是无符号整型,因此需要将new_value进行数据转换:
        但不能直接写为:(int)value,必须写为*(int*)&new_value, 我们来分解一下这条语句的作用:
        1. &new_value获取float new_value的地址
        (int*)将地址从float * 转换为int*以避免64位架构上的精度损失
        *(int*)取消引用地址,最后获取引用的int值
     */
    
    float new_value = 555;
    checkDriver(cuMemsetD32((CUdeviceptr)host_page_locked_memory, *(int*)&new_value, 1)); //??? cuMemset用来干嘛?
    printf("host_page_locked_memory[0] = %f\n", host_page_locked_memory[0]);

    // 释放内存
    checkDriver(cuMemFreeHost(host_page_locked_memory));
    return 0;
}

运行效果如下:

在这里插入图片描述

图1-1 内存分配案例运行效果

这段代码展示了使用 CUDA Driver API 进行设备内存和页锁定内存的分配、访问和释放操作。

首先,代码创建了一个上下文 context,然后使用 cuMemAlloc 函数为 GPU 分配了一个 100 字节的线性内存,并返回了内存的地址。接着,使用 cuMemAllocHost 函数为 CPU 分配了一个 100 字节的页锁定内存,该内存可供 GPU 访问。函数中使用了二级指针,并将其转换成了 void** 类型。之后,代码向页锁定内存中存储了一个值。

接下来,代码使用 cuMemsetD32 函数将页锁定内存中的值进行初始化。该函数的第一个参数是设备指针,需要将 host_page_locked_memory 强制转换为 CUdeviceptr 类型。函数的第二个参数是初始化的值,需要进行数据转换,将 float 类型的值转换为无符号整型。

最后,代码使用 cuMemFreeHost 函数释放了页锁定内存的分配。

关于 cuMemsetD32() 函数有以下几点说明:

  1. host page locked memory 声明的时候为 float* 类型,可以直接转换为 device ptr,只有转换后才可以送到 cuda 核函数(利用DMA(Direct Memory Access)技术)
  2. 初始化内存的值使用的函数:cuMemsetD32(CUdeviceptr dstDevice, unsigned int ui, size_t N)
  3. 初始化值必须是无符号整型,因此需要将 new_value 进行数据转换。但不能直接写 (int)value,必须写 *(int*)&new_value
  4. &new_value 获取 float new_value 的地址,(int*) 将地址从 float * 转换为 int * 避免 64 位架构上的精度损失,*(int *) 取消引用地址,最后获取引用的 int 值
  5. cuMemset 也可以用于在设备内存中设置值,但它设置的是字节数据(unsigned char),而 cuMemsetD32 设置的是 32 位整数数据(unsigned int)

我们注释创建 context 的代码然后再运行下:

在这里插入图片描述

图1-2 context注释报错信息

可以看到程序报错,提示 context 为空,只有当前上下文存在才能做后续的一些操作,

2. 补充知识

我们需要补充下 内存分配 的相关知识:(from 杜老师)

  • 1.统一内存(Unified addressing)

在这里插入图片描述

  • 2.分配线性内存(cuMemAlloc()):

    • 2.1.线性内存:线性内存被组织在单个连续的地址空间中,可以直接以及线性的访问这些内存位置
    • 2.2.内存分配空间以字节为大小,并返回所分配的内存地址
  • 3.分配主机锁页内存(cuMemAllocHost()):

在这里插入图片描述

  • 强力推荐:https://leimao.github.io/blog/Page-Locked-Host-Memory-Data-Transfer/

  • 强力推荐:https://www.youtube.com/watch?v=p9yZNLeOj4s

    在这里插入图片描述

  • 3.1. 页锁定内存:

    • 定义:页面不允许被调入调出的叫页锁定内存,反之叫可分页内存
    • 优点:快
    • a. 设备可以直接访问内存,与可分页内存相比,它的读写带宽要高得多
    • b. 驱动程序会跟踪使用 cuMemAllocHost() 分配得虚拟内存范围,并自动加速对 cuMemcpy() 等函数的调用
    • 使用注意:分配过多页锁定内存会减少系统可用于分页的内存量,可能会降低系统性能。因此,在主机和设备之间为数据交换分配临时区域时,最好少用此功能。
  • 3.2. 这里是从主机分配内存,因此不是输入 device ptr 的地址,而是一个主机的二级地址

  • 4.内存的初始化 cuMemsetD32(CUdeviceptr dstDevice, unsigned int ui, size_t N), 将 N 个 32 位值的内存范围设置为指定的值 ui

  • 5.内存的释放 cuMemFreeHost():有借有还,再借不难

总结

本次课程主要简单讲了下使用 Driver API 进行内存的分配,演示了设备内存、页锁定内存的分配。

Driver API 的学习到这里就结束了,我们不需要有过多的认识,知道 context 是怎么回事,知道 primary context 是怎么回事就行了。

猜你喜欢

转载自blog.csdn.net/qq_40672115/article/details/131606129