难度 : 🔴🔴 高级
预计学习时间 : 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);
}
💡 重点提示
-
位图控制: ACCESS和AIP精确控制访问模式。
-
XGMI检测: 检查hive_id,利用直连优势。
-
迁移策略: best_loc综合考虑preferred、ACCESS、AIP。
-
并发保护: migrate_mutex防止并发迁移冲突。
-
性能权衡: 本地快但需迁移,远程慢但无迁移开销。
⚠️ 常见陷阱
❌ 陷阱1: "所有GPU都设置ACCESS"
- ✅ 正确: 根据访问模式选择ACCESS或AIP。
❌ 陷阱2: "忽略XGMI检测"
- ✅ 正确: 利用XGMI减少迁移。
❌ 陷阱3: "频繁在GPU间迁移"
- ✅ 正确: 使用AIP或保持系统RAM。
📝 实践练习
-
多GPU配置:
bash# 查看GPU互联拓扑 rocm-smi --showtopo # 查看XGMI链接 cat /sys/class/drm/card0/device/xgmi_device_id -
性能测试:
c// 测试本地vs远程访问 measure_bandwidth(local_access); measure_bandwidth(xgmi_remote_access); -
思考题:
- XGMI如何提升多GPU性能?
- 什么时候用ACCESS,什么时候用AIP?
- 如何设计高效的多GPU访问模式?
📚 本章小结
- 多GPU架构: PCIe vs XGMI互联
- 访问位图: ACCESS、AIP、supported
- 最佳位置: 综合考虑preferred、bitmap、hive
- XGMI优化: 直连访问,零拷贝
- 迁移策略: 单hop vs 多hop
- 性能权衡: 本地快,远程灵活
多GPU支持极大扩展了SVM的能力,是大规模计算的基础。
➡️ 下一步
掌握多GPU支持后,下一章学习内存预取与提示。
🔗 导航
- 上一章:10 - SVM中的MMU Notifier集成
- 下一章:审核中...
- 返回目录: AMD ROCm-SVM技术的实现与应用深度分析目录