GDS cuFile

 
Category: Cuda

1. 介绍

cuFile API 是 NVIDIA 为支持 GPUDirect Storage (GDS) 而提供的接口集,它是 CUDA Driver C API 的一部分。

2. 基本接口及其功能描述

cuFileDriverOpen: 初始化驱动程序会话,以支持后续的GDS I/O操作。成功调用此函数后,将建立起与内核驱动的通信

cuFileDriverClose: 关闭驱动程序会话并释放所有与GDS相关的资源。这个步骤通常是在进程结束时隐式完成的,但在某些情况下也可能需要显式地调用它来确保资源的及时释放

cuFileHandleRegister: 将操作系统级别的文件句柄注册到CUDA环境中,这使得应用程序能够通过GPU直接访问文件数据

cuFileHandleDeregister: 一旦完成了对特定文件的操作,应该调用此函数来释放与该文件相关的句柄资源,从而允许系统回收这些资源供其他用途使用

cuFileBufRegister: 注册一个内存缓冲区,以便它可以在GDS操作中被引用。这一步骤确保了指定的缓冲区可以用于直接从存储设备向GPU内存或反之进行数据传输

cuFileBufDeregister: 当不再需要某个已经注册的内存缓冲区时,可以通过此函数释放相关资源

cuFileWrite: 此函数允许应用程序将数据从GPU内存直接写入到注册的文件句柄所指向的存储设备中。通过使用此函数,可以绕过传统的CPU内存路径,从而减少延迟并提高I/O吞吐量

cuFileRead: 此函数用于将数据从存储设备读取到GPU显存中。与cuFileWrite类似,它也避免了CPU内存作为中间介质的需求,实现更高效的直接数据路径。

3.工作流程

初始化: 使用cuFileDriverOpen初始化驱动程序,并确保CUDA环境已经准备好进行GDS操作。

注册文件句柄: 通过cuFileHandleRegister将操作系统级别的文件句柄注册到CUDA环境中,使后续的I/O操作可以直接访问该文件。

注册缓冲区: 如果尚未完成,使用cuFileBufRegister注册参与I/O操作的GPU缓冲区。

执行读/写操作: 根据需求调用cuFileWrite或cuFileRead来进行数据传输。

清理资源: 在完成所有I/O操作后,依次调用cuFileBufDeregister释放缓冲区,cuFileHandleDeregister注销文件句柄,并最终调用cuFileDriverClose关闭驱动程序会话。

4. 示例代码

 
#define MAX_BUFFER_SIZE (4 * 1024 * 1024) 

int main() {
    int fd  = -1;
    ssize_t ret = -1;
    void *writePtr = NULL, *readPtr = NULL; // 分别用于写入和读取的设备指针
    const size_t size = MAX_BUFFER_SIZE;
    CUfileError_t status;
    const char *TESTFILE = "/path/test.file"; 
    CUfileDescr_t cf_descr;
    CUfileHandle_t cf_handle;
    int device_id = 0;

    assert(cudaSetDevice(device_id) == cudaSuccess);

    // 初始化CUDA驱动
    status = cuFileDriverOpen();
    if (status.err != CU_FILE_SUCCESS) {
        std::cerr << "cuFileDriverOpen failed: " << cuFileGetErrorString(status) << std::endl;
        return -1;
    }

    // 分配设备内存
    cudaError_t cudaStatus = cudaMalloc(&writePtr, size);
    if (cudaStatus != cudaSuccess) {
        std::cerr << "cudaMalloc for write buffer failed: " << cudaGetErrorString(cudaStatus) << std::endl;
        cuFileDriverClose();
        return -1;
    }
    
    // 分配另一个设备内存区域用于读取
    cudaStatus = cudaMalloc(&readPtr, size);
    if (cudaStatus != cudaSuccess) {
        std::cerr << "cudaMalloc for read buffer failed: " << cudaGetErrorString(cudaStatus) << std::endl;
        cudaFree(writePtr);
        cuFileDriverClose();
        return -1;
    }

    // 打开文件
    fd = open(TESTFILE, O_CREAT | O_RDWR | O_DIRECT, 0664);
    if (fd == -1) {
        std::cerr << "open failed: " << strerror(errno) << std::endl;
        cudaFree(writePtr);
        cudaFree(readPtr);
        cuFileDriverClose();
        return -1;
    }

    // 注册文件句柄到cuFile
    memset(&cf_descr, 0, sizeof(CUfileDescr_t));
    cf_descr.handle.fd = fd;
    cf_descr.type = CU_FILE_HANDLE_TYPE_OPAQUE_FD;
    status = cuFileHandleRegister(&cf_handle, &cf_descr);
    if (status.err != CU_FILE_SUCCESS) {
        std::cerr << "cuFileHandleRegister failed: " << cuFileGetErrorString(status) << std::endl;
        close(fd);
        cudaFree(writePtr);
        cudaFree(readPtr);
        cuFileDriverClose();
        return -1;
    }

    // 注册设备内存缓冲区
    status = cuFileBufRegister(writePtr, size, 0);
    if (status.err != CU_FILE_SUCCESS) {
        std::cerr << "cuFileBufRegister for write buffer failed: " << cuFileGetErrorString(status) << std::endl;
        cuFileHandleDeregister(cf_handle);
        close(fd);
        cudaFree(writePtr);
        cudaFree(readPtr);
        cuFileDriverClose();
        return -1;
    }

    status = cuFileBufRegister(readPtr, size, 0);
    if (status.err != CU_FILE_SUCCESS) {
        std::cerr << "cuFileBufRegister for read buffer failed: " << cuFileGetErrorString(status) << std::endl;
        cuFileBufDeregister(writePtr);
        cuFileHandleDeregister(cf_handle);
        close(fd);
        cudaFree(writePtr);
        cudaFree(readPtr);
        cuFileDriverClose();
        return -1;
    }

    // 执行写入操作
    ret = cuFileWrite(cf_handle, writePtr, size, 0, 0);
    if (ret < 0 || ret != size) {
        std::cerr << "cuFileWrite failed or incomplete write: " << ret << " vs " << size << std::endl;
        goto cleanup;
    }

    // 执行读取操作
    ret = cuFileRead(cf_handle, readPtr, size, 0, 0);
    if (ret < 0 || ret != size) {
        std::cerr << "cuFileRead failed or incomplete read: " << ret << " vs " << size << std::endl;
        goto cleanup;
    }

cleanup:
    // 清理资源
    cuFileBufDeregister(writePtr);
    cuFileBufDeregister(readPtr);
    cuFileHandleDeregister(cf_handle);
    close(fd);
    cudaFree(writePtr);
    cudaFree(readPtr);

    status = cuFileDriverClose();
    if (status.err != CU_FILE_SUCCESS) {
        std::cerr << "cuFileDriverClose failed: " << cuFileGetErrorString(status) << std::endl;
    }

    return (ret == size) ? 0 : -1;
}