11 - SVM的高级特性:多GPU支持

难度 : 🔴🔴 高级
预计学习时间 : 2-2.5小时
前置知识: 第6-10章、GPU互联技术


📋 概述

多GPU支持是SVM的高级特性,允许单个虚拟地址空间被多个GPU访问。想象一下:

  • 🔗 统一地址空间: 一个指针,多个GPU都能访问
  • 🌐 XGMI互联: GPU间高速直连,低延迟访问
  • 🎯 访问控制: bitmap精确控制哪些GPU可以访问
  • 📍 位置策略: preferred_loc和actual_loc管理数据位置

本章深入多GPU环境下的SVM实现。


11.1 多GPU架构

GPU互联方式

复制代码
方式1: PCIe连接(传统)
┌──────┐     PCIe     ┌──────┐     PCIe     ┌──────┐
│ GPU0 │ ←─────────→  │ CPU  │ ←─────────→  │ GPU1 │
└──────┘              └──────┘              └──────┘
    ↓                    ↓                    ↓
  VRAM0              System RAM              VRAM1

特点:
- 带宽: ~16GB/s (PCIe 4.0 x16)
- 延迟: 高(经过CPU)
- GPU间通信需要通过系统RAM

方式2: XGMI互联(AMD Infinity Fabric)
┌──────┐    XGMI     ┌──────┐    XGMI     ┌──────┐
│ GPU0 │ ←─────────→ │ GPU1 │ ←─────────→ │ GPU2 │
└──────┘             └──────┘             └──────┘
    ↓                    ↓                    ↓
  VRAM0                VRAM1                VRAM2
    ↑                                          ↑
    └───────────── XGMI直连 ──────────────────┘

特点:
- 带宽: ~200GB/s (XGMI 2.0)
- 延迟: 低(直连)
- GPU可直接访问彼此的VRAM
- 形成hive(同一互联域)

SVM中的GPU管理

c 复制代码
struct kfd_process {
    // GPU设备数组
    struct kfd_process_device *pdds[MAX_GPU_INSTANCE];
    uint32_t n_pdds;  // GPU数量
    
    // SVM范围列表
    struct svm_range_list svms;
};

struct kfd_process_device {
    struct kfd_node *dev;        // GPU设备
    uint32_t gpuid;              // GPU唯一ID
    int gpuidx;                  // 数组索引
    
    // 统计信息
    atomic64_t faults;           // 页面错误数
    atomic64_t page_in;          // 迁入页数
    atomic64_t page_out;         // 迁出页数
};

11.2 访问权限位图

三种访问位图

每个SVM范围有三个位图,每位对应一个GPU:

c 复制代码
struct svm_range {
    // ...
    DECLARE_BITMAP(bitmap_access, MAX_GPU_INSTANCE);      // 完全访问
    DECLARE_BITMAP(bitmap_aip, MAX_GPU_INSTANCE);         // 就地访问
    DECLARE_BITMAP(bitmap_supported, MAX_GPU_INSTANCE);   // 支持的GPU
    // ...
};

含义

复制代码
bitmap_access (ACCESS权限):
- GPU有完全访问权限
- 数据应该迁移到该GPU的VRAM
- GPU页面错误时,迁移数据到该GPU

bitmap_aip (ACCESS_IN_PLACE权限):
- GPU可以远程访问(无需迁移)
- 数据保持在原位置
- 通过XGMI或PCIe远程访问

bitmap_supported:
- 该GPU支持SVM
- 由系统自动设置

访问策略示例

场景1:单GPU独占

c 复制代码
// GPU0独占访问,数据在GPU0 VRAM
bitmap_access:     [1, 0, 0, 0]
bitmap_aip:        [0, 0, 0, 0]
preferred_loc:     GPU0
actual_loc:        GPU0

GPU0访问 → 本地VRAM → 快速
GPU1访问 → 无权限 → 错误

场景2:多GPU共享(XGMI)

c 复制代码
// GPU0-3在同一hive,共享访问
bitmap_access:     [1, 1, 1, 1]
bitmap_aip:        [0, 0, 0, 0]
preferred_loc:     GPU1
actual_loc:        GPU1

GPU0访问 → 页面错误 → best_loc=GPU0 → 迁移到GPU0
GPU1访问 → 本地VRAM → 快速
GPU2访问 → 页面错误 → best_loc=GPU2 → 迁移到GPU2

场景3:主从模式(AIP)

c 复制代码
// GPU0主访问,GPU1-3远程访问
bitmap_access:     [1, 0, 0, 0]
bitmap_aip:        [0, 1, 1, 1]
preferred_loc:     GPU0
actual_loc:        GPU0

GPU0访问 → 本地VRAM → 快速
GPU1访问 → XGMI远程访问GPU0 VRAM → 较快
GPU2访问 → XGMI远程访问GPU0 VRAM → 较快

11.3 最佳位置选择

svm_range_best_restore_location复习

在多GPU环境下,选择最佳位置更复杂:

c 复制代码
static int32_t svm_range_best_restore_location(
                    struct svm_range *prange,
                    struct kfd_node *node,  // 故障GPU
                    int32_t *gpuidx)
{
    // 决策优先级:
    
    // 1. preferred_loc == 故障GPU → 迁移到故障GPU
    if (prange->preferred_loc == gpuid)
        return gpuid;
    
    // 2. preferred_loc == 系统内存 → 保持在系统内存
    if (prange->preferred_loc == KFD_IOCTL_SVM_LOCATION_SYSMEM)
        return 0;
    
    // 3. preferred_loc是其他GPU,且在同一hive
    if (prange->preferred_loc != UNDEFINED) {
        preferred_node = svm_range_get_node_by_id(prange,
                                                 prange->preferred_loc);
        if (preferred_node && svm_nodes_in_same_hive(node, preferred_node))
            return prange->preferred_loc;  // 保持在preferred_loc
    }
    
    // 4. 故障GPU有ACCESS权限 → 迁移到故障GPU
    if (test_bit(*gpuidx, prange->bitmap_access))
        return gpuid;
    
    // 5. 故障GPU有AIP权限
    if (test_bit(*gpuidx, prange->bitmap_aip)) {
        if (!prange->actual_loc)
            return 0;  // 保持在系统内存
        
        bo_node = svm_range_get_node_by_id(prange, prange->actual_loc);
        if (bo_node && svm_nodes_in_same_hive(node, bo_node))
            return prange->actual_loc;  // 远程访问actual_loc
        else
            return 0;  // 不在同一hive,保持系统内存
    }
    
    // 6. 无权限
    return -1;
}

多GPU访问模式

模式1:数据并行(分散)

复制代码
数据: 大数组 [0-10000]
GPU0处理: [0-2500]     → 数据在GPU0 VRAM
GPU1处理: [2500-5000]  → 数据在GPU1 VRAM
GPU2处理: [5000-7500]  → 数据在GPU2 VRAM
GPU3处理: [7500-10000] → 数据在GPU3 VRAM

优点: 每个GPU访问本地数据,最快
实现: 设置不同范围的preferred_loc

模式2:模型并行(共享)

复制代码
数据: 神经网络权重
GPU0: 前向传播
GPU1: 反向传播
GPU2: 梯度计算

配置:
    bitmap_aip: [1, 1, 1, 0]
    preferred_loc: GPU0
    actual_loc: GPU0

效果: 数据在GPU0,其他GPU通过XGMI访问

模式3:流水线(迁移)

复制代码
阶段1: GPU0预处理 → 数据在GPU0
阶段2: GPU1计算   → 数据迁移到GPU1
阶段3: GPU2后处理 → 数据迁移到GPU2

配置:
    bitmap_access: [1, 1, 1, 0]
    preferred_loc: UNDEFINED(跟随访问)

11.4 XGMI与Hive

XGMI检测

c 复制代码
// 检查两个GPU是否在同一hive
bool svm_nodes_in_same_hive(struct kfd_node *node1,
                           struct kfd_node *node2)
{
    if (node1->adev->gmc.xgmi.connected_to_cpu)
        return true;  // APU,全部在一个hive
    
    return (node1->adev->gmc.xgmi.hive_id ==
            node2->adev->gmc.xgmi.hive_id) &&
           (node1->adev->gmc.xgmi.hive_id != 0);
}

// 检查是否可以直接映射
bool amdgpu_xgmi_same_hive(struct amdgpu_device *adev1,
                          struct amdgpu_device *adev2)
{
    return adev1->gmc.xgmi.hive_id == adev2->gmc.xgmi.hive_id &&
           adev1->gmc.xgmi.hive_id;
}

XGMI映射优化

c 复制代码
// 在svm_range_map_to_gpu中
if (bo_adev && pdd->dev->adev != bo_adev &&
    !amdgpu_xgmi_same_hive(pdd->dev->adev, bo_adev)) {
    pr_debug("cannot map to device idx %d\n", gpuidx);
    continue;  // 不在同一hive,跳过
}

// 可以直接映射远程VRAM
r = svm_range_map_to_gpu(pdd, prange, offset, npages,
                        readonly, prange->dma_addr[gpuidx],
                        bo_adev,  // VRAM所在设备
                        &fence, flush_tlb);

XGMI映射示例

复制代码
GPU0 VRAM: 物理地址 0x80000000
GPU1想访问:
    → 映射虚拟地址0x1000到GPU0的0x80000000
    → GPU1访问0x1000 → 硬件路由到GPU0 VRAM
    → 无需拷贝!

11.5 多GPU迁移

跨GPU迁移

c 复制代码
// 从GPU0 VRAM迁移到GPU1 VRAM
int svm_migrate_vram_to_vram(struct svm_range *prange,
                            uint32_t src_gpu_id,
                            uint32_t dst_gpu_id,
                            unsigned long start,
                            unsigned long last)
{
    // 1. 从GPU0 VRAM迁移到系统RAM
    r = svm_migrate_vram_to_ram(prange, mm, start, last,
                               KFD_MIGRATE_TRIGGER_PREFETCH,
                               NULL);
    
    // 2. 从系统RAM迁移到GPU1 VRAM
    r = svm_migrate_ram_to_vram(prange, dst_gpu_id,
                               start, last, mm,
                               KFD_MIGRATE_TRIGGER_PREFETCH);
    
    return r;
}

直接XGMI拷贝(理想情况):

复制代码
GPU0 VRAM → SDMA拷贝 → GPU1 VRAM
    ↓          ↓          ↓
 0x8000     XGMI总线   0x9000

无需经过系统RAM!

多GPU并发访问

复制代码
场景: GPU0和GPU1同时访问同一范围

时间线:
T0: GPU0访问 → 故障 → 迁移到GPU0 VRAM
T1: GPU1访问 → 故障
    → best_loc决策:
       如果GPU1有ACCESS → 迁移到GPU1
       如果GPU1有AIP且在同hive → XGMI访问GPU0 VRAM

关键: prange->migrate_mutex防止并发迁移

11.6 用户API

设置访问权限

c 复制代码
// 用户空间API
struct kfd_ioctl_svm_attribute {
    uint64_t start;
    uint64_t size;
    uint32_t type;
    uint32_t value;
};

// 示例1: 设置GPU0有完全访问权限
attrs[0].type = KFD_IOCTL_SVM_ATTR_ACCESS;
attrs[0].value = KFD_IOCTL_SVM_FLAG_GPU_EXEC | 
                KFD_IOCTL_SVM_FLAG_GPU_READ_MOSTLY;
attrs[0].gpu_id = gpu0_id;

// 示例2: 设置GPU1就地访问
attrs[1].type = KFD_IOCTL_SVM_ATTR_ACCESS_IN_PLACE;
attrs[1].value = 0;
attrs[1].gpu_id = gpu1_id;

// 示例3: 设置preferred位置
attrs[2].type = KFD_IOCTL_SVM_ATTR_PREFERRED_LOC;
attrs[2].value = gpu0_id;

// 调用ioctl
ioctl(kfd_fd, AMDKFD_IOC_SVM_SET_ATTR, &args);

Prefetch到指定GPU

c 复制代码
// Prefetch到GPU1
struct kfd_ioctl_svm_attribute attr;
attr.type = KFD_IOCTL_SVM_ATTR_PREFETCH_LOC;
attr.value = gpu1_id;
attr.start = addr;
attr.size = size;

ioctl(kfd_fd, AMDKFD_IOC_SVM_SET_ATTR, &attr);

11.7 性能考虑

本地 vs 远程访问性能

复制代码
访问类型          带宽          延迟
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
本地VRAM         ~1TB/s        ~100ns
XGMI远程VRAM     ~200GB/s      ~500ns
PCIe系统RAM      ~16GB/s       ~1us
PCIe远程VRAM     ~16GB/s       ~2us

最佳实践

1. 利用XGMI

c 复制代码
// 检测XGMI连接
if (svm_nodes_in_same_hive(node0, node1)) {
    // 使用AIP,避免迁移
    set_access_in_place(prange, gpu1_id);
}

2. 数据局部性

c 复制代码
// 将数据放在最常访问的GPU
set_preferred_loc(prange, most_used_gpu_id);

3. 避免频繁迁移

c 复制代码
// 如果多个GPU轮流访问,保持在系统RAM
if (多GPU访问模式) {
    set_preferred_loc(prange, KFD_IOCTL_SVM_LOCATION_SYSMEM);
}

💡 重点提示

  1. 位图控制: ACCESS和AIP精确控制访问模式。

  2. XGMI检测: 检查hive_id,利用直连优势。

  3. 迁移策略: best_loc综合考虑preferred、ACCESS、AIP。

  4. 并发保护: migrate_mutex防止并发迁移冲突。

  5. 性能权衡: 本地快但需迁移,远程慢但无迁移开销。


⚠️ 常见陷阱

陷阱1: "所有GPU都设置ACCESS"

  • ✅ 正确: 根据访问模式选择ACCESS或AIP。

陷阱2: "忽略XGMI检测"

  • ✅ 正确: 利用XGMI减少迁移。

陷阱3: "频繁在GPU间迁移"

  • ✅ 正确: 使用AIP或保持系统RAM。

📝 实践练习

  1. 多GPU配置

    bash 复制代码
    # 查看GPU互联拓扑
    rocm-smi --showtopo
    
    # 查看XGMI链接
    cat /sys/class/drm/card0/device/xgmi_device_id
  2. 性能测试

    c 复制代码
    // 测试本地vs远程访问
    measure_bandwidth(local_access);
    measure_bandwidth(xgmi_remote_access);
  3. 思考题

    • XGMI如何提升多GPU性能?
    • 什么时候用ACCESS,什么时候用AIP?
    • 如何设计高效的多GPU访问模式?

📚 本章小结

  • 多GPU架构: PCIe vs XGMI互联
  • 访问位图: ACCESS、AIP、supported
  • 最佳位置: 综合考虑preferred、bitmap、hive
  • XGMI优化: 直连访问,零拷贝
  • 迁移策略: 单hop vs 多hop
  • 性能权衡: 本地快,远程灵活

多GPU支持极大扩展了SVM的能力,是大规模计算的基础。


➡️ 下一步

掌握多GPU支持后,下一章学习内存预取与提示


🔗 导航

相关推荐
DeeplyMind13 天前
09 - SVM缺页处理机制
svm·amdgpu·rocm·kfd·rocr
DeeplyMind14 天前
07 - SVM内存迁移机制
svm·amdgpu·rocm·kfd·rocr
DeeplyMind14 天前
06 - SVM范围管理
svm·amdgpu·rocm·kfd
啊阿狸不会拉杆15 天前
《机器学习导论》第 13 章-核机器
人工智能·python·算法·机器学习·支持向量机·svm·核机器
DeeplyMind16 天前
05 - 进程与SVM的关系
svm·amdgpu·rocm·kfd
DeeplyMind17 天前
03 - AMDGPU驱动架构概览
svm·amdgpu·rocm·kfd
DeeplyMind18 天前
ROCm rocr-libhsakmt分析系列4: HsaMemFlags分析
rocm·rocr·libhsakmt·hsamemflags
DeeplyMind19 天前
附录A:AMDGPU SVM 属性类型
kfd·amdgpu svm
DeeplyMind20 天前
04 - SVM核心数据结构详解
svm·amdgpu·kfd