所属系列:[Xe SVM 内核实现学习系列]


1. 测试目的

xe_multigpu_svm.c 是学习系列中规模最大的测试文件,包含 22 个子测试,覆盖了以下核心多 GPU SVM 问题:

  1. 同一块系统内存能否被多个 GPU 的独立 VM 访问(SVM 的核心价值主张)
  2. devmem_fd 扩展语义DRM_IOCTL_XE_MADVISEdevmem_fd 字段不只是 0/-1,还能接受另一个 GPU 的 DRM fd,要求内核将 preferred_loc 指向那块 VRAM
  3. P2P 互联可用性检测-ENOLINK 降级策略
  4. 并发原子操作的正确性DRM_XE_ATOMIC_GLOBAL 语义下,200 次双 GPU 并发原子递增的总和精确等于 400
  5. prefetch 对 page fault 的抑制DRM_XE_VM_BIND_OP_PREFETCH + DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC 组合
  6. CONFLICT 场景:每个 GPU 都将数据的 preferred_loc 设为对方的 VRAM,制造迁移方向的竞争

2. 测试基础设施

2.1 GPU 枚举与数据结构

struct xe_svm_gpu_info {
    bool         supports_faults;           /* xe_supports_faults() 的结果(注意取反)*/
    int          vram_regions[MAX_XE_REGIONS]; /* VRAM region instance 列表 */
    unsigned int num_regions;
    unsigned int va_bits;
    int          fd;                        /* DRM fd */
};

get_device_info() 通过 __drm_open_driver_another(i, DRIVER_XE) 逐一枚举系统中所有 xe 设备,最多 8 个。fixture 中要求 gpu_cnt >= 2,否则整个测试跳过。

open_pagemaps() 在结构体中填充 VRAM region instance 列表(通过 XE_IS_VRAM_MEMORY_REGION 过滤)。

2.2 create_vm_and_queue()

每个使用该 helper 的 GPU 都创建独立的 VM:

*vm = xe_vm_create(gpu->fd,
                   DRM_XE_VM_CREATE_FLAG_LR_MODE |
                   DRM_XE_VM_CREATE_FLAG_FAULT_MODE, 0);
*exec_queue = xe_exec_queue_create(gpu->fd, *vm, eci, 0);
xe_vm_bind_lr_sync(gpu->fd, *vm, 0, 0,
                   0, 1ull << gpu->va_bits,
                   DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR);

两个 GPU 各有自己的 VM,但它们的 CPU_ADDR_MIRROR 都镜像同一个进程虚拟地址空间。因此 aligned_alloc() 返回的 CPU 虚拟地址,在两个 GPU 的 VM 中指向的是相同的物理内存(通过各自的 HMM fault 路径)。

2.3 for_each_gpu_pair() 遍历

for (int src = 0; src < num_gpus; src++) {
    if (!gpus[src].supports_faults) continue;
    for (int dst = 0; dst < num_gpus; dst++) {
        if (src == dst) continue;
        fn(&gpus[src], &gpus[dst], eci, flags);
    }
}

对 N 个 GPU 产生 N×(N−1)N \times (N-1)N×(N1) 个有序对,每个 pair 都运行一次。两 GPU 系统产生 2 次调用(GPU0→GPU1、GPU1→GPU0)。

2.4 Flag 系统

Flag 含义
MULTIGPU_PREFETCH BIT(1) 执行前调用 xe_vm_prefetch_async()
MULTIGPU_XGPU_ACCESS BIT(2) 跨 GPU 内存复制场景
MULTIGPU_ATOMIC_OP BIT(3) 原子递增场景
MULTIGPU_COH_OP BIT(4) 一致性写-读验证
MULTIGPU_COH_FAIL BIT(5) 并发写竞争(race condition 观察)
MULTIGPU_PERF_OP BIT(6) 延迟测量场景
MULTIGPU_PERF_REM_COPY BIT(7) 延迟测量中的远端 copy 模式
MULTIGPU_PFAULT_OP BIT(8) page fault 计数验证
MULTIGPU_CONC_ACCESS BIT(9) 并发原子操作(200 次)
MULTIGPU_CONFLICT BIT(10) madvise 指向对方 GPU 的 VRAM(归属冲突)
MULTIGPU_MIGRATE BIT(11) 三阶段迁移验证

3. 关键 API:xe_multigpu_madvise()

这是贯穿整个测试文件的核心 helper,封装了跨 GPU madvise 的降级逻辑:

static void xe_multigpu_madvise(int src_fd, uint32_t vm, uint64_t addr, uint64_t size,
                                uint64_t ext, uint32_t type, int dst_fd, uint16_t policy,
                                uint32_t instance, uint32_t exec_queue)
{
    if (src_fd != dst_fd) {
        /* 尝试将 preferred_loc 指向另一个 GPU 的 VRAM */
        ret = __xe_vm_madvise(src_fd, vm, addr, size, ext, type,
                              dst_fd, policy, instance);
        if (ret == -ENOLINK) {
            /* 两 GPU 之间没有快速 P2P 互联,降级到本地 VRAM */
            ret = __xe_vm_madvise(..., DRM_XE_PREFERRED_LOC_DEFAULT_DEVICE, ...);
            if (ret)
                /* 本地 VRAM 也不可用,最终降级到系统内存 */
                xe_vm_madvise(..., DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM, ...);
        }
    } else {
        /* 本地 GPU:直接用 fd 作为 devmem_fd */
        xe_vm_madvise(src_fd, vm, addr, size, ext, type, dst_fd, policy, instance);
    }
}

devmem_fd 的扩展语义

DRM_IOCTL_XE_MADVISEpreferred_mem_loc.devmem_fd 字段接受三种值:

devmem_fd 含义
DRM_XE_PREFERRED_LOC_DEFAULT_DEVICE = 0 fault tile 自身的 VRAM
DRM_XE_PREFERRED_LOC_DEFAULT_SYSTEM = -1 系统内存
另一个 GPU 的 DRM fd(正整数) 那个 GPU 的 VRAM(需要 P2P 互联)

内核在 xe_vm_madvise_ioctl() 中处理第三种情况时,通过 fget(devmem_fd) 获取对应 GPU 的文件对象,调用 xe_vma_resolve_pagemap() 时找到该设备的 VRAM drm_pagemap。若两设备间没有 P2P 链路,返回 -ENOLINK

-ENOLINK 的含义:内核检测到 src GPU 与 dst GPU 之间没有高速互联(如 NVLink/xGMI 等),无法保证 P2P 直接访问的性能,因此拒绝将另一 GPU 的 VRAM 设为 preferred_loc。

gpu_madvise_exec_sync():madvise + prefetch + exec 三合一

大多数场景通过此 helper 驱动:

static void
gpu_madvise_exec_sync(struct xe_svm_gpu_info *gpu, struct xe_svm_gpu_info *xgpu,
                      uint32_t vm, uint32_t exec_queue, uint64_t dst_addr,
                      uint64_t *batch_addr, unsigned int flags, double *perf)
{
    if (flags & MULTIGPU_CONFLICT)
        /* CONFLICT:故意设为对方 GPU 的 VRAM */
        xe_multigpu_madvise(gpu->fd, vm, dst_addr, ..., xgpu->fd, ...);
    else
        /* 正常:设为本 GPU 的 VRAM */
        xe_multigpu_madvise(gpu->fd, vm, dst_addr, ..., gpu->fd, ...);

    /* 可选:prefetch 到 madvise 指定的位置 */
    xe_multigpu_prefetch(gpu->fd, vm, dst_addr, SZ_4K, ...);

    /* 执行 batch */
    xe_exec_sync(gpu->fd, exec_queue, *batch_addr, &sync, 1);
}

xe_multigpu_prefetch():利用 madvise 驱动 prefetch

static void xe_multigpu_prefetch(int src_fd, uint32_t vm, uint64_t addr, uint64_t size,
                                 struct drm_xe_sync *sync, uint64_t *sync_addr,
                                 uint32_t exec_queue, unsigned int flags)
{
    if (flags & MULTIGPU_PREFETCH) {
        xe_vm_prefetch_async(src_fd, vm, 0, 0, addr, size, sync, 1,
                             DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC);
        /* 等待 prefetch 完成 */
        if (*sync_addr != sync->timeline_value)
            xe_wait_ufence(...);
    }
}

DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC = -1:告诉内核的 PREFETCH 操作"去哪里查 madvise",即迁移到之前 madvise 设置的 preferred_loc。这使 madvise + prefetch 成为一个两步式显式迁移 API:先设意图,再触发迁移。


4. 七类场景详解

4.1 xgpu-access:单 VM,GPU1 执行复制到 GPU2 的 VRAM

对应子测试:mgpu-xgpu-access-basic / mgpu-xgpu-access-prefetch
核心函数:copy_src_dst()

场景设计

/* 只创建 GPU1 的 VM */
create_vm_and_queue(gpu1, eci, &vm[0], &exec_queue[0]);

copy_src = aligned_alloc(..., SZ_64M);
copy_dst = aligned_alloc(..., SZ_64M);

/* batch:MEM_COPY copy_src → copy_dst(64MB) */
batch_init(gpu1->fd, vm[0], to_user_pointer(copy_src),
           to_user_pointer(copy_dst), COPY_SIZE, ...);

/* 设置 copy_dst 的 preferred_loc 为 GPU2 的 VRAM */
xe_multigpu_madvise(gpu1->fd, vm[0],
                    to_user_pointer(copy_dst), COPY_SIZE,
                    0, DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
                    gpu2->fd, ...);

/* prefetch dst 到 GPU2 VRAM(仅 prefetch 子测试) */
xe_multigpu_prefetch(gpu1->fd, vm[0], to_user_pointer(copy_dst), ...);

/* GPU1 执行 copy */
xe_exec_sync(gpu1->fd, exec_queue[0], batch_addr, ...);

igt_assert(memcmp(copy_src, copy_dst, COPY_SIZE) == 0);

关键点

  • 只有 GPU1 有 VM,操作全部由 GPU1 发起
  • copy_dst 的 preferred_loc 是 GPU2 的 VRAM(通过 devmem_fd = gpu2->fd
  • 如果有 P2P 互联:GPU1 执行 copy 时,copy_dst 已迁移到 GPU2 VRAM,GPU1 通过 P2P 链路写入
  • 如果无 P2P(-ENOLINK 降级):copy_dst 可能留在系统内存或本地 VRAM

这个场景验证的核心问题:GPU1 能否正确操作"preferred to GPU2"的内存?


4.2 atomic-op:原子增量跨 GPU 来回流转

对应子测试:mgpu-atomic-op-basic / mgpu-atomic-op-prefetch / mgpu-atomic-op-conflict
核心函数:atomic_inc_op()

数据流(以无 CONFLICT 为例):

初始:data = ATOMIC_OP_VAL - 1 = 55

步骤 1:GPU1 对 addr 执行 MI_ATOMIC_INC
        (gpu_madvise_exec_sync: gpu=GPU1, xgpu=GPU2, CONFLICT模式下→GPU2 VRAM)
        addr → 56

步骤 2:GPU2 执行 MEM_COPY:addr → copy_dst
        (gpu_madvise_exec_sync: gpu=GPU2, xgpu=GPU1, copy_dst→GPU2 VRAM)
        copy_dst → 56
        断言:copy_dst == ATOMIC_OP_VAL ✓

步骤 3:GPU2 对 copy_dst 执行 MI_ATOMIC_INC
        (gpu_madvise_exec_sync: gpu=GPU2, xgpu=GPU1, copy_dst→GPU1 VRAM)
        copy_dst → 57

步骤 4:GPU1 执行 MEM_COPY:copy_dst → addr
        (gpu_madvise_exec_sync: gpu=GPU1, xgpu=GPU2, addr→GPU2 VRAM)
        addr → 57
        断言:addr == ATOMIC_OP_VAL + 1 ✓

两个 VM 都能访问同一块内存addraligned_alloc() 返回的 CPU 虚拟地址,GPU1 的 VM 和 GPU2 的 VM 都通过 CPU_ADDR_MIRROR 的 SVM fault 路径访问它,对应的是同一块物理内存。每次 madvise 改变 preferred_loc,fault 处理器会将数据迁移到新的位置。

MI_ATOMIC | MI_ATOMIC_INC:这是 MI(Media Interface)层的原子递增命令,对 DWORD 地址做 fetch-and-add(+1)。当 DRM_XE_ATOMIC_GLOBAL 被设置时,这个原子操作对 CPU 也是可见的(全局原子语义)。


4.3 coherency:写-读一致性与并发写竞争

对应子测试:mgpu-coherency-basic / mgpu-coherency-fail-basic / mgpu-coherency-prefetch /
           mgpu-coherency-fail-prefetch / mgpu-coherency-conflict
核心函数:coherency_test_multigpu()

基础场景(第一阶段,所有 coherency 子测试都经过):

/* GPU1:MI_STORE_DWORD addr = BATCH_VALUE(60) */
gpu_batch_create(gpu1, vm[0], exec_queue[0], addr, 0, DWORD);
gpu_madvise_exec_sync(gpu1, gpu2, vm[0], exec_queue[0], addr, ...);
/* addr 现在是 60 */

/* GPU2:MEM_COPY addr → copy_dst */
gpu_batch_create(gpu2, vm[1], exec_queue[1], addr,
                 to_user_pointer(copy_dst), INIT);
gpu_madvise_exec_sync(gpu2, gpu1, vm[1], exec_queue[1],
                      to_user_pointer(copy_dst), ...);

/* 验证:copy_dst == 60 */
igt_assert_eq(*(uint32_t *)copy_dst, BATCH_VALUE);

这个阶段验证:GPU1 写入后,GPU2 能读到正确的值。写操作通过 SVM fault + madvise 可能触发了迁移,但 GPU2 读到的值必须是 GPU1 最后写入的值——这正是"一致性"的含义。

MULTIGPU_COH_FAIL 扩展场景(并发写竞争):

/* 两个 GPU 同时向 addr 写不同的值,没有等待顺序 */
store_dword_batch_init(gpu1->fd, vm[0], addr, BATCH_VALUE + 10);  /* 70 */
store_dword_batch_init(gpu2->fd, vm[1], addr, BATCH_VALUE + 20);  /* 80 */

xe_exec_sync(gpu1->fd, exec_queue[0], batch1_addr[0], &sync0, 1);
xe_exec_sync(gpu2->fd, exec_queue[1], batch1_addr[1], &sync1, 1);
/* 两个 exec 之间没有等待 —— 真正的并发竞争 */

/* 等待双方完成 */
xe_wait_ufence(gpu1->fd, sync_addr0, ...);
xe_wait_ufence(gpu2->fd, sync_addr1, ...);

/* GPU2 读 addr 的最终值 */
gpu_batch_create(gpu2, ..., addr, to_user_pointer(result), INIT);
gpu_madvise_exec_sync(gpu2, gpu1, vm[1], exec_queue[1],
                      to_user_pointer(result), ...);

coh_result = READ_ONCE(*result);
if (coh_result == 70)      igt_info("GPU1's write won");
else if (coh_result == 80) igt_info("GPU2's write won");
else if (coh_result == 0)  igt_warn("Both writes failed");
else                       igt_warn("Unexpected corruption");

注意:这个场景没有 igt_assert 来要求特定的赢家,因为 race condition 的结果是不确定的。它的目的是观察并记录行为,验证:

  1. 不会发生数据损坏(结果必须是 70 或 80 之一,不能是其他值)
  2. 没有内核崩溃或 GPU hang

4.4 latency:性能测量与阈值验证

对应子测试:mgpu-latency-basic / mgpu-latency-prefetch /
           mgpu-latency-copy-basic / mgpu-latency-copy-prefetch
核心函数:latency_test_multigpu()

测量方式clock_gettime(CLOCK_MONOTONIC) 包裹整个 gpu_madvise_exec_sync()(含 madvise + prefetch + exec + 等待)。

两种子场景

MULTIGPU_PERF_REM_COPY(latency-copy-*):
  GPU1 写 → GPU2 从 GPU1 的内存读 copy_dst → 测量 GPU2 的延迟
  这是真正的"跨 GPU 远端读取"延迟

否则(latency-basic / latency-prefetch):
  GPU1 写 → GPU1 再写 → GPU2 copy → 测量 GPU1+GPU2 的总延迟

阈值(软警告,不强制失败)

场景 期望延迟 警告条件
GPU1 写 + prefetch < 5 µs > 5 µs
GPU2 远端 copy + prefetch < 1 µs > 1 µs
GPU2 远端 copy(无 prefetch) < 10 µs > 10 µs
GPU2 本地写 + prefetch < 1 µs > 1 µs

这些是 igt_warn(软警告)而非 igt_assert,允许性能有一定波动。延迟测量的主要目的是检验 prefetch 是否有效减少了 fault 带来的延迟开销


4.5 pagefault:page fault 计数的精确验证

对应子测试:mgpu-pagefault-basic / mgpu-pagefault-prefetch / mgpu-pagefault-conflict
核心函数:pagefault_test_multigpu()

读取 GT 统计计数器

const char *pf_count_stat = "svm_pagefault_count";
pf_count_before = xe_gt_stats_get_count(fd, eci->gt_id, pf_count_stat);
/* ... 执行操作 ... */
pf_count_after = xe_gt_stats_get_count(fd, eci->gt_id, pf_count_stat);

xe_gt_stats_get_count() 读取 /sys/kernel/debug/dri/<minor>/gt<N>/stats/<name> 中的计数。

两阶段验证

Phase 1:GPU1 写

  • 有 prefetch → igt_assert_eq(pf_after, pf_before)严格要求零 fault
  • 无 prefetch → igt_debug(...) → 只记录,不要求

Phase 2:GPU2 跨 GPU 读 GPU1 的内存

/* GPU2 专门对 addr(GPU1 的内存)再做一次 madvise + prefetch */
xe_multigpu_madvise(gpu2->fd, vm[1], addr, SZ_4K, 0,
                    DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
                    gpu2->fd, 0, gpu2->vram_regions[0], ...);
xe_multigpu_prefetch(gpu2->fd, vm[1], addr, SZ_4K, ...);
  • 有 prefetch → igt_assert_eq(pf_after, pf_before)GPU2 访问也必须零 fault
  • 无 prefetch → igt_debug(...) → 观察

这个场景的核心意义:验证 prefetch 确实"提前迁移"了数据,使 GPU 在执行时不需要触发 page fault。这是 DRM_XE_VM_BIND_OP_PREFETCH + DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC 的正确性测试。


4.6 concurrent-access:200 次双 GPU 并发原子操作

对应子测试:mgpu-concurrent-access-basic / mgpu-concurrent-access-prefetch
核心函数:multigpu_access_test()

这是最复杂的场景,专门验证 DRM_XE_ATOMIC_GLOBAL 语义:

/* 两个 GPU 各设置 ATOMIC_GLOBAL + 偏好自己的 VRAM */
xe_multigpu_madvise(gpu1->fd, vm[0], addr, SZ_4K, 0,
                    DRM_XE_MEM_RANGE_ATTR_ATOMIC, DRM_XE_ATOMIC_GLOBAL, 0, 0, ...);
xe_multigpu_madvise(gpu1->fd, vm[0], addr, SZ_4K, 0,
                    DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC,
                    gpu1->fd, 0, gpu1->vram_regions[0], ...);
/* GPU2 同样操作 */

/* 两个 GPU 各 prefetch addr 到自己的 VRAM */
xe_multigpu_prefetch(gpu1->fd, vm[0], addr, SZ_4K, ...);
xe_multigpu_prefetch(gpu2->fd, vm[1], addr, SZ_4K, ...);

/* 循环 NUM_ITER=200 次,两个 GPU 并发提交原子递增 */
for (int i = 0; i < NUM_ITER; i++) {
    bool last = (i == NUM_ITER - 1);

    xe_exec_sync(gpu1->fd, exec_queue[0], batch_addr[0],
                 last ? &sync[0] : NULL, last ? 1 : 0);
    xe_exec_sync(gpu2->fd, exec_queue[1], batch_addr[1],
                 last ? &sync[1] : NULL, last ? 1 : 0);
}
/* 只等最后一次的 fence */
xe_wait_ufence(gpu1->fd, sync_addr[0], EXEC_SYNC_VAL + NUM_ITER - 1, ...);
xe_wait_ufence(gpu2->fd, sync_addr[1], EXEC_SYNC_VAL + NUM_ITER - 1, ...);

/* 用 GPU1 读取最终值 */
gpu_batch_create(gpu1, vm[0], ..., addr, to_user_pointer(verify_result), INIT);
xe_exec_sync(gpu1->fd, exec_queue[0], verify_batch_addr, &sync[0], 1);

final_value = READ_ONCE(*(uint32_t *)verify_result);
igt_assert_f(final_value == 2 * NUM_ITER,  /* 0 + 200 + 200 = 400 */
             "Expected %u value, got %u\n", 2 * NUM_ITER, final_value);

关键设计决策

  • 最终验证不用 CPU 直接读:CPU 读 MEMORY_DEVICE_PRIVATE 的 VRAM 页会触发回迁,可能产生不确定性;改用 GPU 读并 copy 到系统内存后再 CPU 读
  • 只等最后一次 fence:200 次 exec 全部提交到各自的 exec_queue,由驱动串行执行,只需等队列尾部的 fence
  • 断言精确 = 400DRM_XE_ATOMIC_GLOBAL 保证每次 MI_ATOMIC_INC 是原子的,200 × 2 = 400 不会多也不会少

DRM_XE_ATOMIC_GLOBAL 的含义(来自 uapi):

“Support both GPU and CPU atomic operations.”

内核在 xe_svm_handle_pagefault() 中看到 ATOMIC_GLOBAL 属性时,会在建立 GPU 页表时启用硬件原子访问标志(PPGTT_PTE_ATOMIC 等),同时也保持 CPU 的原子访问路径。


4.7 migration:三阶段数据迁移验证

对应子测试:mgpu-migration-basic / mgpu-migration-prefetch
核心函数:multigpu_migrate_test()

三个迁移阶段

Phase 1: System Memory → GPU1 VRAM
  CPU: WRITE_ONCE(*data, 0x12345678)   /* 系统内存 */
  GPU1: MI_STORE_DWORD addr = 0xDEADBEEF
        madvise: preferred = GPU1 VRAM → prefetch → exec
  结果:addr 在 GPU1 VRAM,值 = 0xDEADBEEF

Phase 2: GPU1 VRAM → GPU2 VRAM
  GPU2: MEM_COPY addr → copy_dst
        madvise: copy_dst preferred = GPU2 VRAM → prefetch → exec
  结果:copy_dst 在 GPU2 VRAM,值 = 0xDEADBEEF
  断言:*(uint32_t *)copy_dst == 0xDEADBEEF ✓

Phase 3: GPU2 VRAM → System Memory(CPU 读触发回迁)
  GPU2: MI_STORE_DWORD copy_dst = 0xCAFEBABE
        madvise: copy_dst preferred = GPU2 VRAM → prefetch → exec
  结果:copy_dst 在 GPU2 VRAM,值 = 0xCAFEBABE
  CPU: READ_ONCE(*(uint32_t *)copy_dst) ← CPU 读触发 HMM 页面回迁
  断言:result == 0xCAFEBABE ✓

Phase 3 的关键:CPU 直接读取 MEMORY_DEVICE_PRIVATE 类型的 VRAM 页会触发 MMU fault,内核通过 drm_pagemap_migrate_to_ram() 将数据从 VRAM DMA 回系统内存后,CPU 才能读到正确的值。这验证了VRAM→系统内存的反向迁移路径


5. CONFLICT 场景:madvise 归属冲突

MULTIGPU_CONFLICT flag 在 gpu_madvise_exec_sync() 中改变 madvise 的目标:

if (flags & MULTIGPU_CONFLICT)
    /* GPU1 的操作:preferred = GPU2 的 VRAM */
    xe_multigpu_madvise(gpu, vm, addr, ..., xgpu->fd, ...);
else
    /* GPU1 的操作:preferred = GPU1 自己的 VRAM */
    xe_multigpu_madvise(gpu, vm, addr, ..., gpu->fd, ...);

for_each_gpu_pair() 的两个方向中:

  • GPU1→GPU2 方向:GPU1 的操作将 addr 的 preferred_loc 设为 GPU2 的 VRAM
  • GPU2→GPU1 方向:GPU2 的操作将 addr 的 preferred_loc 设为 GPU1 的 VRAM

这制造了一种情况:每次 GPU 操作前都改变数据的"归属地",使数据在两个 GPU 的 VRAM 之间不断被迁移。CONFLICT 子测试检验这种持续迁移下系统的稳定性(不崩溃、不死锁)以及数据的正确性(尽管数据最终可能在任意位置,功能断言仍应通过)。


6. prefetch 与 fault 的对比:pagefault-basic vs pagefault-prefetch

步骤 pagefault-basic(无 prefetch) pagefault-prefetch(有 prefetch)
madvise preferred = GPU1 VRAM
prefetch ✗(数据仍在系统内存) ✓(数据已迁移到 GPU1 VRAM)
GPU1 执行 STORE_DWORD GPU1 访问 addr → SVM page fault → 迁移 → 再执行 直接访问,无 fault
svm_pagefault_count 变化 增加(debug 记录) 不变(assert 验证)
GPU2 跨 GPU 读 GPU2 访问 addr → fault → 再迁移 prefetch 到 GPU2 VRAM → 无 fault

prefetch 的代价是:需要显式等待 xe_vm_prefetch_async() 完成(通过 user fence)。收益是:后续 GPU 执行时零 fault,消除了 fault 处理的延迟。这个 trade-off 在实时渲染或计算密集型任务中尤为重要。


7. 多 VM 访问同一块内存:SVM 的核心特性

进程虚拟地址 addr(aligned_alloc 返回)

GPU1 的 VM(CPU_ADDR_MIRROR + FAULT_MODE)
  └── GPU 访问 addr → SVM fault → HMM pin → GPU1 页表 entry
      └── 若 preferred = GPU1 VRAM → 迁移 → GPU1 VRAM DPA

GPU2 的 VM(CPU_ADDR_MIRROR + FAULT_MODE)
  └── GPU 访问 addr → SVM fault → HMM pin → GPU2 页表 entry
      └── 若 preferred = GPU2 VRAM → 迁移 → GPU2 VRAM DPA

两个 VM 的 SVM range 对同一 CPU VA 的处理是独立的。每个 VM 有自己的 drm_gpusvm 实例,维护自己的 xe_svm_range 链表。当 GPU1 迁移数据到其 VRAM 后,GPU2 的 xe_svm_range 对应的物理地址仍然是系统内存(GPU1 的 range 只更新了 GPU1 的 GPU 页表)。GPU2 再次 fault 时,会重新 HMM pin,得到 GPU1 VRAM 的物理地址(VRAM 页已是 MEMORY_DEVICE_PRIVATE,HMM 通过 devmem 路径处理)。

这就是多 VM 共享 SVM 内存的机制:物理位置可以移动,但 CPU 虚拟地址是稳定的锚点;每个 VM 在自己的 fault 路径中独立解析当前物理位置。


涉及文件

文件 作用
tests/intel/xe_multigpu_svm.c 本文档分析的测试文件
include/uapi/drm/xe_drm.h DRM_XE_CONSULT_MEM_ADVISE_PREF_LOCDRM_XE_VM_BIND_OP_PREFETCHDRM_XE_ATOMIC_*
drivers/gpu/drm/xe/xe_vm.c madvise ioctl 中 devmem_fd→drm_pagemap 解析、-ENOLINK 返回条件
drivers/gpu/drm/xe/xe_svm.c 多 VM 下 drm_gpusvm 实例的独立性、DRM_XE_ATOMIC_GLOBAL 在 fault 路径的处理
drivers/gpu/drm/xe/xe_gt_pagefault.c svm_pagefault_count 统计自增位置
lib/xe/xe_ioctl.c xe_vm_prefetch_async()xe_vm_madvise() 的 IGT 封装
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐