RDMA-Verbs-GPU 内存与 DMA-BUF(十一)

GPU DMA-BUF 把 GPU allocation 变成可由其他设备使用的共享对象。本文以 AMD GPU 软件栈为主线,说明 HSA 指针如何关联 KFD allocation 与 GEM BO,AMDGPU 如何导出 DMA-BUF,以及 RNIC 注册该 fd 后 attach、pin 与 map 流程。

1. 概述

GPU DMA-BUF 路径包含三个不同问题:exporter 先把 GPU allocation 封装为共享对象;importer 再取得面向自身设备的 DMA mapping;应用最后负责安排 GPU 和 RDMA 的执行顺序。下图分成四段:

  1. hipMalloc() 返回 GPU pointer。ROCr 在 allocation map 中定位包含该指针的 allocation,libhsakmt 把指针转换成 KFD handle 和导出对象内 offset。
  2. AMDKFD 由 handle 找到 kgd_mem,再通过 GEM PRIME 导出 amdgpu_bo。amdgpu_gem_prime_export() 把 DMA-BUF OPS 设置为 amdgpu_dmabuf_ops。
  3. 应用把 fd、offset、length 和 RDMA IOVA 传给 ibv_reg_dmabuf_mr()。RNIC importer 经 attach 和 map 取得 SG/DMA address;仅 pinned 分支显式执行 pin。
  4. HCA 使用 MR IOVA 和 key 访问映射后的 GPU backing storage。应用以 GPU signal/stream completion 和 RDMA CQ completion 为同步点,在确认前一设备完成访问后再允许另一设备使用 buffer,从而保证 GPU 与 RNIC 之间的访问顺序。

2. 用户态程序

2.1 接口

下面的示例从 ROC_SHMEM 和 RCCL 的实际 DMA-BUF 注册方式中提取主要代码,并用普通 Verbs 表达一次 RDMA WRITE。

接口定义 功能
hipError_t hipMalloc(void **ptr, size_t sizeBytes); 在当前 HIP device 上分配 GPU memory,成功后把 GPU pointer 写入 ptr。
hipError_t hipFree(void *ptr); 释放 hipMalloc() 返回的 allocation。调用前必须停止使用该 allocation 的 GPU 与 RDMA 工作。
hipError_t hipStreamSynchronize(hipStream_t stream); 等待指定 stream 中此前提交的命令完成;示例用它建立 GPU producer 到 RNIC consumer 的顺序。
hsa_status_t hsa_amd_portable_export_dmabuf(const void *ptr, size_t size, int *dmabuf, uint64_t *offset); 导出包含 [ptr, ptr + size) 的单个 allocation,返回底层共享对象的 fd 及 ptr 在对象中的 offset;对象可能大于应用 allocation。
hsa_status_t hsa_amd_portable_export_dmabuf_v2(const void *ptr, size_t size, int *dmabuf, uint64_t *offset, uint64_t flags); v2 增加 mapping type flags;HSA_AMD_DMABUF_MAPPING_TYPE_PCIE 用于请求 PCIe mapping。无此要求时使用 NONE,等价于基础接口。
hsa_status_t hsa_amd_portable_close_dmabuf(int dmabuf); 关闭 HSA 导出的 DMA-BUF fd。调用后,调用者保存的整数值不会自动改变。
struct ibv_mr *ibv_reg_dmabuf_mr(struct ibv_pd *pd, uint64_t offset, size_t length, uint64_t iova, int fd, int access); 把 fd 引用的 [offset, offset + length) 注册为 MR,使用 iova 作为数据面逻辑起点;offset 与 iova 的 system-page 内偏移必须相同。
int ibv_dereg_mr(struct ibv_mr *mr); 注销 MR。调用前应用必须保证没有尚未完成的 WR 继续引用该 MR。
int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr); 把发送 WR 提交给 QP;返回成功只表示 WR 已被软件接受,不表示 HCA 已完成访问。
int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc); 从 CQ 取得 WC。示例等待成功的发送 completion 后才撤销 MR。

2.2 简化示例

GPU VA 用作 MR IOVA;VA 须与接口返回的 offset 保持相同页内偏移,不能从应用 allocation 起点自行推算。

c 复制代码
#include <hip/hip_runtime.h>
#include <hsa/hsa_ext_amd.h>
#include <infiniband/verbs.h>
#include <stdint.h>
#include <stdlib.h>

struct rdma_endpoint {
    struct ibv_pd *pd;
    struct ibv_qp *qp;
    struct ibv_cq *cq;
    uint64_t remote_addr;
    uint32_t remote_rkey;
};

static int wait_send_completion(struct rdma_endpoint *ep)
{
    struct ibv_wc wc;

    for (;;) {
        int n = ibv_poll_cq(ep->cq, 1, &wc);
        if (n < 0)
            return -1;
        if (n == 0)
            continue;
        return wc.status == IBV_WC_SUCCESS && wc.wr_id == 1 &&
               wc.qp_num == ep->qp->qp_num && wc.opcode == IBV_WC_RDMA_WRITE ? 0 : -1;
    }
}

int write_gpu_buffer(struct rdma_endpoint *ep, hipStream_t producer,
                     size_t length)
{
    void *gpu_buf = NULL;
    struct ibv_mr *mr = NULL;
    int dmabuf_fd = -1;
    uint64_t dmabuf_offset = 0;
    int rc = -1;

    if (length == 0 || length > UINT32_MAX)
        return -1;
    if (hipMalloc(&gpu_buf, length) != hipSuccess)
        goto out;

    /* 真实程序先在 producer stream 中生成 gpu_buf 的内容。 */
    if (hipStreamSynchronize(producer) != hipSuccess)
        goto out;

    if (hsa_amd_portable_export_dmabuf(gpu_buf, length,
                                       &dmabuf_fd,
                                       &dmabuf_offset) != HSA_STATUS_SUCCESS)
        goto out;

    mr = ibv_reg_dmabuf_mr(ep->pd, dmabuf_offset, length,
                           (uint64_t)(uintptr_t)gpu_buf,
                           dmabuf_fd,
                           IBV_ACCESS_LOCAL_WRITE |
                           IBV_ACCESS_REMOTE_READ |
                           IBV_ACCESS_REMOTE_WRITE);
    if (mr == NULL)
        goto out;

    struct ibv_sge sge = {
        .addr = (uint64_t)(uintptr_t)gpu_buf,
        .length = (uint32_t)length,
        .lkey = mr->lkey,
    };
    struct ibv_send_wr wr = {
        .wr_id = 1,
        .sg_list = &sge,
        .num_sge = 1,
        .opcode = IBV_WR_RDMA_WRITE,
        .send_flags = IBV_SEND_SIGNALED,
        .wr.rdma.remote_addr = ep->remote_addr,
        .wr.rdma.rkey = ep->remote_rkey,
    };
    struct ibv_send_wr *bad_wr = NULL;

    if (ibv_post_send(ep->qp, &wr, &bad_wr) != 0)
        goto out;
    if (wait_send_completion(ep) != 0)
        abort();

    rc = 0;

out:
    if (mr != NULL && ibv_dereg_mr(mr) != 0)
        abort();
    if (dmabuf_fd >= 0)
        hsa_amd_portable_close_dmabuf(dmabuf_fd);
    if (gpu_buf != NULL)
        hipFree(gpu_buf);
    return rc;
}

3. HSA 与 KFD

3.1 Allocation 查找

HIP allocation 最终登记在 ROCr 的 allocation map 中。Runtime::DmaBufExport() 使用 upper_bound(ptr) 找到不大于 ptr 的候选记录,再检查 ptr 和 ptr + size 是否都位于同一 allocation。这里的关键结果不是重新创建 BO,而是确认 allocation base、allocation size 和 ptr 的相对位置。

hsa_amd_portable_export_dmabuf() 支持 allocation 子区间。fd 引用底层共享对象,offset 是 ptr 相对该对象的偏移;存在子分配时,对象起点不等于应用 allocation 起点。注册应使用接口返回的 offset。

3.2 导出调用

下图展示从 HSA API 到 KFD ioctl 的调用层次。图中省略 API table wrapper,保留指针查找、handle 转换和 ioctl 三个改变对象语义的节点。

  1. hsa_amd_portable_export_dmabuf() 只做 API 参数检查,然后进入 Runtime::DmaBufExport()。
  2. ROCr 查找 allocation 并检查 GPU agent;libhsakmt 再从 aperture 找到 vm_object_t,计算子区间 offset,并取 obj->handles[0]。
  3. AMDKFD_IOC_EXPORT_DMABUF 接收的是 KFD handle,不是原始 GPU VA;用户指针到 handle 的转换已经在 libhsakmt 完成。

3.3 KFD 对象

kfd_ioctl_export_dmabuf() 根据 handle 中的 GPU id 选择 kfd_process_device,再通过 IDR handle 取得 kgd_mem。kgd_mem 是 KFD 对 allocation 的内核描述,主线相关字段如下。

c 复制代码
struct kgd_mem {
    /* allocation 对应的 AMDGPU BO */
    struct amdgpu_bo *bo;
    /* 已缓存的导出对象 */
    struct dma_buf *dmabuf;
    /* allocation 的 GPU VA 起点 */
    uint64_t va;
    /* VRAM/GTT 等允许或当前 domain 信息 */
    uint32_t domain;
    /* KFD DRM client 中引用同一 GEM object 的 handle */
    uint32_t gem_handle;
    /* 可写、显存类型等 allocation 属性 */
    uint32_t alloc_flags;
};

KFD handle 用于进程内核对象查找,gem_handle 用于 DRM PRIME 导出,DMA-BUF fd 用于跨子系统共享;三者都是句柄,但所属命名空间和生命周期不同。

4. AMDGPU 导出

4.1 BO 与 GEM

kgd_mem->bo 指向 amdgpu_bo,其 TTM base 同时承载 drm_gem_object。KFD allocation 因而可以通过 gem_handle 进入通用 DRM PRIME 导出,而不需要复制 VRAM/GTT 内容。

kfd_mem_export_dmabuf() 只在 mem->dmabuf 为空时创建导出对象。重复导出复用缓存对象,amdgpu_amdkfd_gpuvm_export_dmabuf() 再执行 get_dma_buf(),为当前调用方返回独立引用。

4.2 PRIME 导出

下图说明 KFD allocation 如何通过 GEM 和 DRM PRIME 导出为 DMA-BUF,以及 AMDGPU object-specific exporter 与通用 exporter 的关系。

  1. KFD handle 同时编码 GPU ID 和 allocation handle;kfd_device_by_id() 确定设备,kfd_process_device_translate_handle() 以进程设备上下文中的 alloc_idr 为边界,将其转换为 kgd_mem。
  2. 导出路径包含 allocation 级 mem->dmabuf、GEM object 级 obj->dma_buf 和 drm_file 级 PRIME handle cache。amdgpu_amdkfd_gpuvm_export_dmabuf() 与 drm_gem_prime_handle_to_dmabuf() 使重复导出复用同一 DMA-BUF,同时为每个调用方建立独立引用。
  3. export_and_register_object() 把 object-specific exporter 作为优先策略。AMDGPU 路径通过 obj->funcs->export() 进入 amdgpu_gem_prime_export(),完成 TTM export 准备并安装 amdgpu_dmabuf_ops;通用 fallback 则保留 drm_gem_prime_export() 创建的默认 ops。
  4. dma_buf_fd() 成功后,导出引用由 fd 接管;如果 fd 安装失败,kfd_ioctl_export_dmabuf() 必须通过 dma_buf_put() 撤销该引用。因此 fd 承载跨子系统传递的 DMA-BUF 引用,而不是另一个 GPU allocation。

4.3 Exporter 操作表

amdgpu_dmabuf_ops 把通用 DMA-BUF 生命周期连接到 AMDGPU BO:

c 复制代码
const struct dma_buf_ops amdgpu_dmabuf_ops = {
    .attach = amdgpu_dma_buf_attach,
    .pin = amdgpu_dma_buf_pin,
    .unpin = amdgpu_dma_buf_unpin,
    .map_dma_buf = amdgpu_dma_buf_map,
    .unmap_dma_buf = amdgpu_dma_buf_unmap,
    .release = drm_gem_dmabuf_release,
    .begin_cpu_access = amdgpu_dma_buf_begin_cpu_access,
    .mmap = drm_gem_dmabuf_mmap,
    .vmap = amdgpu_dma_buf_vmap,
    .vunmap = amdgpu_dma_buf_vunmap,
};

当前操作表没有 AMDGPU 专用 .detach。detach 仍然是完整生命周期的一步,但 DMA-BUF Core 只从 attachment list 删除对象并释放 attachment;AMDGPU 的设备映射清理发生在 unmap/unpin。

5. AMDGPU DMA-BUF

5.1 Attach、Pin 与 Map

下图从 RDMA Core 的 DMA-BUF 内存获取入口展开 exporter 回调。图中展示 pinned/KSM 分支;普通 MTT 分支跳过 dma_buf_pin(),但复用相同的 attach 和 map 机制。

  1. Attach 确定当前 importer 能否直接访问 VRAM,但不在此时改变 BO placement。dma_buf_dynamic_attach() 以 importer 的 allow_peer2peer 初始化 attach->peer2peer,amdgpu_dma_buf_attach() 再按 buffer 属性和设备拓扑检查该能力:
    1. 允许 P2P 需同时满足三个条件:importer 声明 allow_peer2peer=true;BO 不是 GFX12+ 上启用 AMDGPU_GEM_CREATE_GFX12_DCC 的 surface;amdgpu_dmabuf_is_xgmi_accessible() 或 pci_p2pdma_distance() >= 0 证明存在可达路径。
    2. mlx5 attachment 的 dma_buf_attach_adev() 返回 NULL,所以 XGMI 判断恒为不可达;它只有在 pci_p2pdma_distance() 返回非负值时才保留 P2P 能力。
    3. importer 不允许 P2P、对象为 GFX12+ DCC surface,或 XGMI 与 PCI P2P 都不可达时,AMDGPU 只清除 attach->peer2peer,attachment 本身仍可建立。amdgpu_vm_bo_update_shared() 遍历 bo->vm_bo,通过 amdgpu_vm_update_shared() 把相关 VM 的内存统计从 private 更新为 shared,不负责 BO 迁移或 DMA mapping。
  2. Pin 综合全部 attachments 的能力,确定 BO 可固定的位置。amdgpu_dma_buf_pin() 只有在所有 attachments 都保留 P2P 时才允许 VRAM domain;任一 attachment 不支持 P2P 就排除 VRAM,并在 allowed domains 包含 GTT 时固定到 GTT。amdgpu_bo_pin() 对已固定且 domain 兼容的 BO 只增加 pin_count,不发生迁移;首次固定则由 amdgpu_bo_get_preferred_domain() 选择 domain,amdgpu_bo_placement_from_domain() 生成 placement,ttm_bo_validate() 在当前 resource 不满足 placement 时执行迁移,最后由 ttm_bo_pin() 固定位置。
  3. Map 按 BO 的实际 TTM resource 生成面向 RNIC 的 SG/DMA address。已 pin 的 BO 直接映射当前 resource,不再迁移;普通 MTT 分支的 BO 若尚未 pin,amdgpu_dma_buf_map() 以 GTT 为基本候选,只在 BO 偏好 VRAM 且 attachment 保留 P2P 时加入 VRAM,ttm_bo_validate() 可能因此迁移 BO。TT resource 通过 drm_prime_pages_to_sg() 与 dma_map_sgtable() 映射 system pages;VRAM 通过 amdgpu_vram_mgr_alloc_sgt() 与 dma_map_resource() 映射 VRAM aperture;MMIO remap 则由 amdgpu_ttm_mmio_remap_alloc_sgt() 生成 I/O resource mapping。
  4. ib_umem_dmabuf_map_pages() 在 exporter map 之后按 DMA-BUF offset/length 裁剪 SG,并等待 migration fence。这个边界保证 MR 使用的是已定位的 backing storage。

5.2 P2P 与 GTT

AMDGPU 的策略不是"无法 P2P 就注册失败",而是优先选择仍可被 importer 访问的位置。amdgpu_dma_buf_pin() 在所有 attachments 都允许 P2P 时可以把 BO 固定在 VRAM;若任一 attachment 清除了 peer2peer,VRAM 会从 pin domain 中移除,剩余可用位置为 GTT 时就固定到 GTT。部分 AMD DKMS 兼容代码还会在内核缺少 DMA-BUF move-notify 支持时直接排除 VRAM,避免 RDMA 与 GPU 对可移动 BO 的固定要求发生冲突。

amdgpu_dma_buf_map() 对未固定 BO 采用相似策略:GTT 始终是基本候选,VRAM 只有在 BO 偏好 VRAM 且 attachment 允许 P2P 时才加入候选。最终位置仍由 TTM validation、BO allowed/preferred domains 和资源状态共同决定,所以"可能回退到 GTT"比"必然复制到 GTT"更准确。

5.3 begin_cpu_access

amdgpu_dma_buf_begin_cpu_access() 不是 RNIC 注册路径的一部分,而是 DMA-BUF 被 CPU 访问前的 exporter 回调。它有两类直接调用来源:用户态对 DMA-BUF fd 发出 DMA_BUF_IOCTL_SYNC,由 dma_buf_ioctl() 转入 dma_buf_begin_cpu_access();内核消费者也可以直接调用该通用接口。

  1. dma_buf_begin_cpu_access() 先调用 amdgpu_dmabuf_ops.begin_cpu_access。AMDGPU 只有在访问方向包含 CPU 读取且 BO 支持 GTT 时才考虑改变 placement;纯写访问或不支持 GTT 的 BO 保持原位置。
  2. 需要优化 CPU 读取时,回调 reserve BO。已 pin 的 BO 不能迁移;未 pin 且 allowed_domains 包含 GTT 的 BO 才会生成 GTT placement,并由 ttm_bo_validate() 在必要时迁移,最后解除 reserve。
  3. exporter 回调成功后,DMA-BUF Core 再通过 dma_resv_wait_timeout() 等待隐式同步 fence。该等待不覆盖显式同步事务,CPU 实际读写仍应与 dma_buf_end_cpu_access() 成对出现。

5.4 mmap

drm_gem_dmabuf_mmap() 服务于用户态 VMA:用户对 DMA-BUF fd 执行 mmap() 时由 dma_buf_mmap_internal() 进入,importer 已持有 dma_buf 时也可以调用 dma_buf_mmap() helper。两种入口完成范围检查后都调用 exporter 的 .mmap,再由该 DRM helper 转入 drm_gem_prime_mmap()。

  1. drm_gem_prime_mmap() 先加入 GEM VMA offset,再在两条 mmap 路径中二选一。对象提供 .mmap 时直接调用具体的 amdgpu_gem_object_mmap();否则构造临时 DRM file,调用 obj->dev->driver->fops->mmap(),AMDGPU 的入口是 drm_gem_mmap()。AMDGPU BO 实际采用前一条路径,后一条是 DRM PRIME 的兼容分支。
  2. amdgpu_gem_object_mmap() 把"禁止 CPU 映射"和"继续建立 TTM VMA"作为互斥结果:usermm BO 或带 AMDGPU_GEM_CREATE_NO_CPU_ACCESS 的 BO 返回 -EPERM;其余 BO 才经 drm_gem_ttm_mmap() 进入 ttm_bo_mmap_obj()。图中省略了仅做 GEM→TTM 转换的 helper。
  3. ttm_bo_mmap_obj() 不立即映射 backing pages,而是增加 BO 引用、把 BO 保存到 vm_private_data,并保留 drm_gem_prime_mmap() 已安装的 amdgpu_gem_vm_ops;真正的数据页映射推迟到 CPU page fault。
  4. Page fault 调用 amdgpu_gem_fault():先 reserve BO,amdgpu_bo_fault_reserve_notify() 保证 placement 对 CPU 可见;不可见且未 pin 的 VRAM BO 可由 ttm_bo_validate() 移到 CPU-visible VRAM 或 GTT,已 pin 的不可见 BO 返回 SIGBUS。随后 ttm_bo_vm_fault_reserved() 对 I/O resource 使用 ttm_bo_io_mem_pfn(),对 system pages 使用 page_to_pfn(),最终由 vmf_insert_pfn_prot() 把 PFN 写入 CPU 页表。

5.5 vmap

amdgpu_dma_buf_vmap() 由 DMA-BUF Core 的 dma_buf_vmap() 调用,面向需要连续内核虚拟地址的内核消费者;dma_buf_vmap_unlocked() 只是先取得 dma_resv 锁再调用它。通过该映射访问数据时,仍需用 dma_buf_begin_cpu_access()/dma_buf_end_cpu_access() 建立一致性边界。

  1. dma_buf_vmap() 先检查 DMA-BUF 的 vmap cache。已有映射时只增加 vmapping_counter 并复用 vmap_ptr,因此 amdgpu_dma_buf_vmap() 只在首次映射时执行。
  2. AMDGPU 先以 GTT | VRAM 调用 amdgpu_bo_pin(),目标是固定一个可映射位置,并不强制迁移到 GTT。随后 drm_gem_dmabuf_vmap() 通过对象回调进入 drm_gem_ttm_vmap();图中省略只负责校验和转发的 drm_gem_vmap_locked()。ttm_bo_vmap() 对 I/O resource 复用 mem->bus.addr,或按 cache 属性调用 ioremap_wc()、ioremap_cache()、ioremap();对 system pages 则先执行 ttm_bo_populate(),再用 vmap() 建立连续的内核虚拟地址映射。
  3. 映射失败时会立即调用 amdgpu_bo_unpin()。正常路径在最后一个 dma_buf_vunmap() 释放缓存映射时进入 amdgpu_dma_buf_vunmap(),先撤销 TTM 映射,再解除 BO pin。

6. 对象与地址

共享路径同时出现对象句柄、虚拟地址、对象内偏移和设备地址。下图按使用顺序,介绍这些概念。

名称 产生者与作用域 使用者 语义
HSA pointer / GPU VA HIP/ROCr 进程 GPU VA 空间 GPU 指令、ROCr allocation map、应用 指向 allocation 内的某个字节,不一定是 allocation base。
KFD handle libhsakmt 保存的 allocation 对象 AMDKFD ioctl 在 KFD 进程/设备对象表中定位 kgd_mem,不是数据面地址。
amdgpu_bo / GEM object AMDKFD、AMDGPU、DRM TTM、GEM PRIME、DMA-BUF exporter 表示 backing storage 及其 placement、reservation 与引用。
VRAM/GTT TTM placement GPU、AMDGPU、HCA 表示 BO 当前或允许的内存位置,不是另一套应用虚拟地址。
DMA-BUF fd dma_buf_fd() 安装到进程 fd table 用户态传递、dma_buf_get() 引用共享对象;关闭 fd 只释放 fd 自身引用。
DMA-BUF offset libhsakmt 返回的对象内偏移 ibv_reg_dmabuf_mr()、SG 裁剪 在底层共享对象内定位注册起点。
DMA address exporter 与 DMA API 面向 attachment device 生成 RNIC DMA/IOMMU、MR 地址翻译 SG 中供 RNIC 使用的设备地址;启用 IOMMU 转换时再映射到目标物理地址。
RDMA IOVA 应用传入 Verbs SGE、远端请求、MKey 地址检查 MR 的逻辑地址起点,不等于 DMA-BUF offset,也不等于 DMA address。
lkey / rkey RNIC MR 注册结果 本地 SGE 或远端请求 与 RDMA IOVA 一起选择 MR、权限和地址转换。

设 HSA 返回的 offset 为 d,注册长度为 L、iova = V,则区间内地址 V + n 对应共享对象偏移 d + n。d 不一定是相对应用 allocation 的偏移;RNIC 通过 MR 翻译将 IOVA 区间映射到 SG 的 DMA 地址段。

7. P2P 数据访问

7.1 两条路径

下图并列展示 VRAM P2P 与 GTT 路径。两条路径对应用暴露相同的 DMA-BUF MR 和 IOVA,但 HCA 最终访问的位置不同。

VRAM 路径要求 attachment 保持 peer2peer,BO 允许并偏好 VRAM,且平台能为 RNIC 建立有效 P2P DMA address。HCA 经 PCIe P2P 访问 GPU VRAM,CPU 不参与业务数据复制。AMDGPU 对 GFX12 DCC surface 主动禁止这一路径,因为设备本地压缩元数据不适合普通 peer 直接访问。

GTT 路径使用 system pages。GPU 通过 GART/GPUVM 访问这些页,RNIC 则通过主机 DMA mapping 访问同一 backing storage。它通常适用于更广泛的拓扑,但数据位置和性能特征不再等同于直接访问 VRAM。

7.2 拓扑条件

源码直接使用 pci_p2pdma_distance() 判断 importer device 与 exporter GPU 的 P2P 可达性。实际部署还需要同时检查 PCIe root complex、switch、ACS、IOMMU mode、BAR/peer aperture、DMA mask、GPU/RNIC 驱动和固件能力。仅凭 IOMMU 或 ACS 的存在不能决定结果;应以平台形成的实际 P2P mapping 和 exporter 返回的 SG 为准。

8. 同步边界

8.1 Reservation 与 Fence

AMDGPU GEM BO 的 reservation object 同时作为导出 DMA-BUF 的 dma_resv。attachment、pin、map 和 mapping invalidation 使用该锁保护 placement 与映射变化;ib_umem_dmabuf_map_pages() 在得到 SG 后等待 DMA_RESV_USAGE_KERNEL fence,确保 exporter 的迁移完成,SG 指向的 backing storage 已经稳定。

8.2 GPU 与 RDMA

下图使用 UML 时序图显示两种所有权的交接顺序。参与者从左到右为 GPU stream、应用线程、RNIC/HCA 和远端应用,时间自上而下推进。

GPU producer→RNIC consumer 的安全顺序是:GPU kernel 写入数据,应用观察 HSA signal、HIP event 或 stream completion,再提交引用该 MR 的 WR。RNIC producer→GPU consumer 的安全顺序是:HCA 完成本地 DMA,应用取得相应 WC,再把依赖传入 GPU stream 或在 CPU 侧提交后续 kernel。

CQ completion 的精确含义取决于 opcode 和本地/远端角色。发送侧 RDMA WRITE completion 允许本端复用源 buffer,但不能保证远端已经收到数据。

9. 释放与生命周期

9.1 释放调用

下图聚焦 MR importer 的清理分支;fd close 与 allocation free 是独立入口。三类引用可以在不同时间消失,最终通过 DMA-BUF/GEM 引用计数汇合。

  1. 应用先停止提交新的 WR,并等待引用 MR 的 GPU/RDMA 工作完成。
  2. ibv_dereg_mr() 进入 RNIC 注销路径,最终由 RDMA DMA-BUF umem 执行 revoke、unmap、detach 和 dma_buf_put();pinned 分支还执行 unpin。
  3. fd close 释放 fd table 持有的 DMA-BUF 引用;它可以早于 MR 注销,但不能替代 MR 注销。
  4. hipFree() 释放应用/KFD allocation 引用;DMA-BUF、GEM 或 importer 仍持有引用时,backing memory 要等到最后一个引用消失后才释放。

9.2 Importer 清理

ib_umem_dmabuf_release() 先在 reservation lock 下调用 revoke。revoke 撤销 SG mapping,并在 pinned 状态下调用 dma_buf_unpin();随后 dma_buf_detach() 删除 attachment,dma_buf_put() 释放 importer 的共享对象引用。

AMDGPU 的 unmap 对 GTT SG 调用 dma_unmap_sgtable() 并释放 SG,对 VRAM SG 调用 VRAM manager 的 free 接口;unpin 则调用 amdgpu_bo_unpin(),允许 BO 再次移动。AMDGPU 没有 .detach 回调,因此 detach 本身只完成 DMA-BUF Core attachment 管理。

9.3 BO 回收

fd、kgd_mem->dmabuf cache、每个 importer 和 GEM object 都可能持有独立引用。关闭 fd 不会使仍在使用的 MR 失效;注销 MR 也不会自动关闭应用 fd;hipFree() 不保证 backing storage 立即消失。drm_gem_dmabuf_release() 释放 DMA-BUF 对 GEM object 的引用,只有 KFD/GEM、fd 和 importer 引用全部消失,BO 才具备最终回收条件。

错误回滚遵循相反顺序:map 失败先撤销已建立的 mapping/pin,attachment 创建后若发生失败,则执行 detach/put,用户态注册失败时,关闭 fd 并释放 allocation。示例正常回收时进入 out,无法确认完成或注销失败时终止进程,不继续释放 buffer。

10. NVIDIA DMA-BUF

10.1 导出

CUDA Driver API 提供以下 DMA-BUF 导出接口:

接口定义 功能
CUresult cuMemGetHandleForAddressRange(void *handle, CUdeviceptr dptr, size_t size, CUmemRangeHandleType handleType, unsigned long long flags); 当 handleType 为 CU_MEM_RANGE_HANDLE_TYPE_DMA_BUF_FD 时,把 fd 写入 handle。dptr 和 size 必须按 host page 对齐;应用应先查询 CU_DEVICE_ATTRIBUTE_DMA_BUF_SUPPORTED。
int close(int fd); 关闭 CUDA 返回的 DMA-BUF fd;CUDA 接口没有与 AMD HSA close 对应的专用关闭入口。

当前 CUDA 文档还定义了 CU_MEM_RANGE_FLAG_DMA_BUF_MAPPING_TYPE_PCIE:支持的平台可要求经 PCIe BAR1 建立 DMA-BUF,否则接口返回错误。NVSHMEM 在 data-direct 模式使用该 flag,但在普通模式把 flags 设为 0。

导出之后,普通 RNIC 路径仍调用同一个 ibv_reg_dmabuf_mr(pd, offset, length, iova, fd, access)。NVSHMEM 先把 pointer 向下对齐到 host page,把 size 向上取整,然后以 offset 0 注册对齐后的范围;AMD HSA 接口则直接返回原 pointer 在导出的 DMA-BUF 对象中的 offset。

10.2 部署差异

项目 AMD ROCm NVIDIA CUDA
Allocation hipMalloc() 或 HSA memory pool cuMemAlloc(),或 reserve/map 完整覆盖的 VMM range
导出接口 hsa_amd_portable_export_dmabuf() cuMemGetHandleForAddressRange()
用户态层次 ROCr → libhsakmt → /dev/kfd CUDA Driver → NVIDIA kernel driver
Exporter AMDKFD/amdgpu GEM PRIME NVIDIA GPU driver DMA-BUF exporter
Offset 接口返回导出的 DMA-BUF 对象内 offset 常见用法先做 host-page 对齐,再导出对齐 range
fd 关闭 hsa_amd_portable_close_dmabuf() 或等价 close close()
RNIC 接口 ibv_reg_dmabuf_mr() ibv_reg_dmabuf_mr()
P2P 请求 v2 API 可带 HSA_AMD_DMABUF_MAPPING_TYPE_PCIE;AMDGPU 再按 attachment/topology 选择 placement 可使用 CU_MEM_RANGE_FLAG_DMA_BUF_MAPPING_TYPE_PCIE 请求 PCIe BAR1 mapping
传统路径 依赖 ROCm/网络库的普通 MR 或其他 peer-memory 路径 legacy nvidia-peermem/peer-memory 路径

DMA-BUF 与 nvidia-peermem 是两种部署路径。DMA-BUF 依赖 CUDA/驱动暴露 exporter 和 Linux DMA-BUF importer;legacy peer-memory 由 nvidia-peermem 向 RDMA peer-memory client 提供页面映射。应用应按 CUDA device attribute、Verbs symbol、内核驱动模式和平台拓扑做运行时探测,不能仅以 CUDA 版本号推断可用性。

11. 总结

  1. GPU 内存可直接导出为 DMA-BUF,无需复制内容;fd 引用共享对象,接口返回的 offset 定位注册区间。
  2. DMA-BUF 为 GPU exporter 与 RNIC importer 提供统一接口。AMD 通过 amdgpu_dmabuf_ops 支持 attachment、pin 和 DMA mapping,NVIDIA 通过 CUDA Driver API 导出 fd;两者都可使用 ibv_reg_dmabuf_mr() 注册 MR,但能力判断和部署依赖不同。
  3. RNIC 直接访问 VRAM 取决于 BO 属性、attachment 能力和 PCIe 拓扑。P2P 条件满足时可以建立 VRAM SG;条件不满足且 BO 允许 GTT 时可以回退到 GTT,但非 P2P allocation 不一定能够注册成功。
  4. GPU VA、DMA-BUF offset、DMA address 和 RDMA IOVA 属于不同地址空间。MR key、IOVA 和 SG 共同建立 RDMA 地址到 GPU allocation backing storage 的映射。
  5. begin_cpu_access、mmap 和 vmap 分别承担 CPU 访问同步、用户态映射和内核态映射,与 RNIC 注册路径相互独立。
  6. dma_resv/fence、GPU stream/HSA signal 和 Work Completion 分别约束内存迁移、GPU 工作和 RDMA 操作。应用必须显式连接这些同步域,才能安全地在 GPU 与 RNIC 之间交接 buffer 所有权。
  7. MR、attachment、DMA-BUF fd 和 GPU allocation 持有独立引用。相关 GPU/RDMA 工作完成后才能撤销这些资源;只有全部引用都已释放,AMDGPU BO 及其 backing storage 才能最终回收。

12. 参考资料

12.1 源码位置

功能 目录 文件
HSA DMA-BUF API 与 ROCr 导出 rocm-systems/projects/rocr-runtime/runtime/hsa-runtime/ inc/hsa_ext_amd.h、core/runtime/hsa_ext_amd.cpp、core/runtime/runtime.cpp
libhsakmt pointer/handle 转换 rocm-systems/projects/rocr-runtime/libhsakmt/ src/memory.c、src/fmm.c、include/hsakmt/hsakmt.h
AMDKFD ioctl 与 GPUVM 对象 linux/drivers/gpu/drm/amd/ amdkfd/kfd_chardev.c、amdgpu/amdgpu_amdkfd.h、amdgpu/amdgpu_amdkfd_gpuvm.c
AMDGPU exporter 与 P2P mapping linux/drivers/gpu/drm/amd/amdgpu/ amdgpu_dma_buf.c、amdgpu_dma_buf.h
DRM PRIME 与 DMA-BUF Core linux/drivers/gpu/drm/、linux/drivers/dma-buf/ drm_prime.c、dma-buf.c、dma-resv.c
Verbs DMA-BUF 接口 rdma-core/libibverbs/、rdma-core/providers/mlx5/ verbs.h、verbs.c、cmd_mr.c、providers/mlx5/verbs.c
RDMA DMA-BUF attachment linux/drivers/infiniband/core/ umem_dmabuf.c
AMD 应用用法 rocm-systems/projects/rocshmem/src/gda/、rocm-systems/projects/rccl/src/include/ ibv_wrapper.cpp、provider_gda_mlx5.cpp、rocmwrap.h
NVIDIA 应用与 exporter 对照 nvshmem/src/modules/transport/、open-gpu-kernel-modules/kernel-open/nvidia-drm/ common/transport_ib_common.cpp、ibgda/ibgda.cpp、nvidia-drm-gem-dma-buf.c

12.2 官方文档

  1. Linux Kernel Documentation,Buffer Sharing and Synchronization:linux/Documentation/driver-api/dma-buf.rst。
  2. Linux Kernel Documentation,PCI Peer-to-Peer DMA Support:linux/Documentation/driver-api/pci/p2pdma.rst。
  3. AMD ROCr Runtime,hsa_ext_amd.h 中 hsa_amd_portable_export_dmabuf() 的接口说明。
  4. NVIDIA,CUDA Driver API --- Memory Management :https://docs.nvidia.com/cuda/cuda-driver-api/cuda_driver_api/group__CUDA__MEM.html。
  5. NVIDIA,GPUDirect RDMA :https://docs.nvidia.com/cuda/gpudirect-rdma/。
相关推荐
业余程序员plus3 天前
RDMA-Verbs-文章导读(一)
rdma·qp·rocev2·dma-buf·rdma-core·mlx5·verbs
小此方3 天前
Linux网络(二十九):从内网穿透到P2P打洞:NAT转换、代理机制与局域网通信的底层全景解析
linux·网络·p2p
zoyation11 天前
jrp 1.2.0 重磅发布|P2P打洞直连 · 端口自动分配 · 一端口多服务,这次全齐了!
网络·网络协议·p2p
草根大哥14 天前
07-P2P 直连与节点池调度
webrtc·p2p·srt·whip·whep·自建cdn