1. 概述
本文档梳理 HIP/ROCclr 上层运行时如何把用户的内存建议(hipMemAdvise / hipMemAdvise_v2)一层层翻译成 ROCr 的 SVM 属性设置调用 hsa_amd_svm_attributes_set,再经 ROCr 核心Runtime::SetSvmAttrib 转换为 libhsakmt(thunk)的 hsaKmtSVMSetAttr,最终通过 AMDKFD_IOC_SVM ioctl 由 KFD 内核驱动完成 HMM/SVM range 的属性更新。
这条链路对应 CUDA 的 cudaMemAdvise 语义(read-mostly、preferred location、accessed-by 等),是 ROCm 统一虚拟内存(Unified/Managed Memory, HMM)迁移与访问控制的核心路径。
整条链路经历了 四次属性表示的翻译:
| 层次 | 属性类型 |
|---|---|
| HIP | hipMemoryAdvise 枚举 |
| ROCclr | amd::MemoryAdvice 枚举 |
| ROCr API | hsa_amd_svm_attribute_pair_t(HSA_AMD_SVM_ATTRIB_*) |
| libhsakmt | HSA_SVM_ATTRIBUTE(HSA_SVM_ATTR_* / HSA_SVM_FLAG_*) |
| KFD ioctl | kfd_ioctl_svm_args / drm_amdgpu_svm_attribute(KFD_IOCTL_SVM_ATTR_*) |
2. 调用链全景
#mermaid-svg-AQ3gm055a5loIVA5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AQ3gm055a5loIVA5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AQ3gm055a5loIVA5 .error-icon{fill:#552222;}#mermaid-svg-AQ3gm055a5loIVA5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AQ3gm055a5loIVA5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AQ3gm055a5loIVA5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AQ3gm055a5loIVA5 .marker.cross{stroke:#333333;}#mermaid-svg-AQ3gm055a5loIVA5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AQ3gm055a5loIVA5 p{margin:0;}#mermaid-svg-AQ3gm055a5loIVA5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AQ3gm055a5loIVA5 .cluster-label text{fill:#333;}#mermaid-svg-AQ3gm055a5loIVA5 .cluster-label span{color:#333;}#mermaid-svg-AQ3gm055a5loIVA5 .cluster-label span p{background-color:transparent;}#mermaid-svg-AQ3gm055a5loIVA5 .label text,#mermaid-svg-AQ3gm055a5loIVA5 span{fill:#333;color:#333;}#mermaid-svg-AQ3gm055a5loIVA5 .node rect,#mermaid-svg-AQ3gm055a5loIVA5 .node circle,#mermaid-svg-AQ3gm055a5loIVA5 .node ellipse,#mermaid-svg-AQ3gm055a5loIVA5 .node polygon,#mermaid-svg-AQ3gm055a5loIVA5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AQ3gm055a5loIVA5 .rough-node .label text,#mermaid-svg-AQ3gm055a5loIVA5 .node .label text,#mermaid-svg-AQ3gm055a5loIVA5 .image-shape .label,#mermaid-svg-AQ3gm055a5loIVA5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-AQ3gm055a5loIVA5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AQ3gm055a5loIVA5 .rough-node .label,#mermaid-svg-AQ3gm055a5loIVA5 .node .label,#mermaid-svg-AQ3gm055a5loIVA5 .image-shape .label,#mermaid-svg-AQ3gm055a5loIVA5 .icon-shape .label{text-align:center;}#mermaid-svg-AQ3gm055a5loIVA5 .node.clickable{cursor:pointer;}#mermaid-svg-AQ3gm055a5loIVA5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AQ3gm055a5loIVA5 .arrowheadPath{fill:#333333;}#mermaid-svg-AQ3gm055a5loIVA5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AQ3gm055a5loIVA5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AQ3gm055a5loIVA5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AQ3gm055a5loIVA5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AQ3gm055a5loIVA5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AQ3gm055a5loIVA5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AQ3gm055a5loIVA5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AQ3gm055a5loIVA5 .cluster text{fill:#333;}#mermaid-svg-AQ3gm055a5loIVA5 .cluster span{color:#333;}#mermaid-svg-AQ3gm055a5loIVA5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AQ3gm055a5loIVA5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AQ3gm055a5loIVA5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-AQ3gm055a5loIVA5 .icon-shape,#mermaid-svg-AQ3gm055a5loIVA5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AQ3gm055a5loIVA5 .icon-shape p,#mermaid-svg-AQ3gm055a5loIVA5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AQ3gm055a5loIVA5 .icon-shape rect,#mermaid-svg-AQ3gm055a5loIVA5 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AQ3gm055a5loIVA5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AQ3gm055a5loIVA5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AQ3gm055a5loIVA5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用: hipMemAdvise / hipMemAdvise_v2
(HIP Runtime API)
hip_hmm.cpp
解析 location + advice
amd::Device::SetSvmAttributes
(device.hpp 虚接口)
roc::Device::SetSvmAttributesInt
(rocdevice.cpp)
advice → HSA attribute 映射
Hsa::svm_attributes_set
(rocrctx.hpp 静态包装)
ROCR_DYN(hsa_amd_svm_attributes_set)
(rocrctx.cpp 动态符号)
hsa_amd_svm_attributes_set
(ROCr Runtime / hsa_ext_amd.cpp)
Runtime::SetSvmAttrib
(runtime.cpp)
HSA attrib → HSA_SVM_ATTR / flags
hsaKmtSVMSetAttr
(libhsakmt/src/svm.c)
node_id→gpu_id, 组装 ioctl 参数
hsakmt_ioctl(AMDKFD_IOC_SVM)
(内核 KFD 驱动设置 SVM range 属性)
3. 各层详解
3.1 HIP API 层:hip_hmm.cpp
用户调用 hipMemAdvise(dev_ptr, count, advice, device)(对应 CUDA cudaMemAdvise)。
HIP 层完成参数校验和 location 解析,然后转发到 ROCclr device 层。
关键逻辑(projects/clr/hipamd/src/hip_hmm.cpp):
cpp
// 1. 解析 location(Device / Host / HostNuma / HostNumaCurrent)
int targetDevice = hipCpuDeviceId;
bool use_cpu = true;
switch (location.type) {
case hipMemLocationTypeDevice: // 迁移/偏好到某个 GPU
targetDevice = location.id;
use_cpu = false;
break;
case hipMemLocationTypeHostNuma: // 指定 NUMA 节点
targetDevice = location.id;
use_cpu = true;
break;
// ... HostNumaCurrent / Host
}
// 2. 选择目标 amd::Device
amd::Device* dev = (use_cpu || isAdviseReadMostly)
? g_devices[0]->devices()[0]
: g_devices[targetDevice]->devices()[0];
// 3. 转发到 device 层
if (!dev->SetSvmAttributes(dev_ptr, count,
static_cast<amd::MemoryAdvice>(advice),
use_cpu, targetDevice)) {
return hipErrorInvalidValue;
}
枚举对齐 :
hip_hmm.cpp顶部用一组static_assert保证 HIP 的hipMemAdvise*枚举值与 ROCclr 内部amd::MemoryAdvice枚举值一一对应, 因此可以直接static_cast转换,无需查表映射。
cpp
static_assert(static_cast<uint32_t>(hipMemAdviseSetReadMostly)
== amd::MemoryAdvice::SetReadMostly, "...");
// SetPreferredLocation / SetAccessedBy / SetCoarseGrain ... 同理
3.2 ROCclr 抽象层:amd::Device::SetSvmAttributes
device.hpp 中定义的纯虚接口,屏蔽后端差异(ROCm / PAL):
cpp
// projects/clr/rocclr/device/device.hpp
virtual bool SetSvmAttributes(const void* dev_ptr, size_t count,
amd::MemoryAdvice advice,
bool use_cpu = false, int numa_id = 0) const = 0;
ROCm 后端实现(rocdevice.cpp)只是转调内部实现,first_alloc=false:
cpp
bool Device::SetSvmAttributes(const void* dev_ptr, size_t count,
amd::MemoryAdvice advice,
bool use_cpu, int numa_id) const {
constexpr bool kFirstAlloc = false;
return SetSvmAttributesInt(dev_ptr, count, advice, kFirstAlloc, use_cpu);
}
3.3 核心映射层:Device::SetSvmAttributesInt
这是整条链路的核心------把 amd::MemoryAdvice 翻译成hsa_amd_svm_attribute_pair_t 属性对(rocdevice.cpp)。
advice → HSA 属性映射表
ROCclr amd::MemoryAdvice |
HSA hsa_amd_svm_attribute_t |
value |
|---|---|---|
SetReadMostly |
HSA_AMD_SVM_ATTRIB_READ_MOSTLY |
true |
UnsetReadMostly |
HSA_AMD_SVM_ATTRIB_READ_MOSTLY |
false |
SetPreferredLocation |
HSA_AMD_SVM_ATTRIB_PREFERRED_LOCATION |
CPU/GPU agent handle |
UnsetPreferredLocation |
HSA_AMD_SVM_ATTRIB_PREFERRED_LOCATION |
0(无偏好) |
SetAccessedBy(首次分配) |
HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE |
agent handle |
SetAccessedBy(后续更新) |
HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE_IN_PLACE |
agent handle |
UnsetAccessedBy |
HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE |
agent handle |
SetCoarseGrain |
HSA_AMD_SVM_ATTRIB_GLOBAL_FLAG |
COARSE_GRAINED |
UnsetCoarseGrain |
HSA_AMD_SVM_ATTRIB_GLOBAL_FLAG |
FINE_GRAINED |
关键代码
cpp
bool Device::SetSvmAttributesInt(const void* dev_ptr, size_t count,
amd::MemoryAdvice advice, bool first_alloc,
bool use_cpu, int numa_id) const {
// 仅在硬件支持 HMM 时才真正下发
if (info().hmmSupported_) {
std::vector<hsa_amd_svm_attribute_pair_t> attr;
switch (advice) {
case amd::MemoryAdvice::SetReadMostly:
attr.push_back({HSA_AMD_SVM_ATTRIB_READ_MOSTLY, true});
break;
case amd::MemoryAdvice::SetPreferredLocation:
if (use_cpu) {
attr.push_back({HSA_AMD_SVM_ATTRIB_PREFERRED_LOCATION,
getCpuAgent(numa_id).handle});
} else {
attr.push_back({HSA_AMD_SVM_ATTRIB_PREFERRED_LOCATION,
getBackendDevice().handle});
}
break;
case amd::MemoryAdvice::SetAccessedBy: {
// 首次分配用 AGENT_ACCESSIBLE(允许 page fault 触发迁移)
// 后续更新用 AGENT_ACCESSIBLE_IN_PLACE(原地访问,不迁移)
const uint64_t attrib = (first_alloc)
? HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE
: HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE_IN_PLACE;
if (use_cpu) {
attr.push_back({attrib, getCpuAgent().handle});
} else if (first_alloc) {
// 首次分配:为所有可访问 GPU 开放访问
for (const auto dev : devices()) {
if (static_cast<Device*>(dev)->getBackendDevice().handle != 0) {
attr.push_back({attrib,
static_cast<Device*>(dev)->getBackendDevice().handle});
}
}
} else {
attr.push_back({attrib, getBackendDevice().handle});
}
break;
}
case amd::MemoryAdvice::SetCoarseGrain:
attr.push_back({HSA_AMD_SVM_ATTRIB_GLOBAL_FLAG,
HSA_AMD_SVM_GLOBAL_FLAG_COARSE_GRAINED});
break;
// ... 其余 Unset* 分支
default:
return false;
}
// 下发到 ROCr
hsa_status_t status = Hsa::svm_attributes_set(
const_cast<void*>(dev_ptr), count, attr.data(), attr.size());
if (status != HSA_STATUS_SUCCESS) {
LogPrintfError("hsa_amd_svm_attributes_set() failed. "
"Advice: %d, status: %d", advice, status);
return false;
}
} else {
// 无 HMM 支持则静默忽略(不报错,保持 API 兼容)
LogWarning("hsa_amd_svm_attributes_set() is ignored, because no HMM support");
}
return true;
}
3.4 动态符号包装层:Hsa::svm_attributes_set
ROCclr 不直接链接 ROCr,而是运行时 dlopen + dlsym 加载符号,避免硬依赖 libhsa-runtime64.so。
静态包装(projects/clr/rocclr/device/rocm/rocrctx.hpp):
cpp
static hsa_status_t svm_attributes_set(void* ptr, size_t size,
hsa_amd_svm_attribute_pair_t* attribute_list,
size_t attribute_count) {
return ROCR_DYN(hsa_amd_svm_attributes_set)(
ptr, size, attribute_list, attribute_count);
}
函数指针成员(rocrctx.hpp)与动态加载(rocrctx.cpp):
cpp
// rocrctx.hpp: 保存函数指针
decltype(::hsa_amd_svm_attributes_set)* hsa_amd_svm_attributes_set_;
// rocrctx.cpp: 初始化时从 ROCr 动态解析符号
GET_ROCR_SYMBOL(hsa_amd_svm_attributes_set);
3.5 ROCr 运行时层:hsa_amd_svm_attributes_set
进入 ROCr 实现(runtime/hsa-runtime/core/runtime/hsa_ext_amd.cpp),它只是一层薄封装,实际逻辑转发到 Runtime::SetSvmAttrib:
cpp
hsa_status_t hsa_amd_svm_attributes_set(void* ptr, size_t size,
hsa_amd_svm_attribute_pair_t* attribute_list,
size_t attribute_count) {
TRY;
IS_OPEN();
return core::Runtime::runtime_singleton_->SetSvmAttrib(
ptr, size, attribute_list, attribute_count);
CATCH;
}
API 表分发:
hsa_amd_svm_attributes_set通过hsa_api_trace的amd_ext_api.hsa_amd_svm_attributes_set_fn注册,hsa_table_interface.cpp中的同名符号最终调用到AMD::hsa_amd_svm_attributes_set。
3.6 ROCr 核心翻译层:Runtime::SetSvmAttrib
这是 ROCr 侧的核心(runtime/hsa-runtime/core/runtime/runtime.cpp):把 HSA 公共属性 HSA_AMD_SVM_ATTRIB_* 翻译成 libhsakmt 的 HSA_SVM_ATTRIBUTE(HSA_SVM_ATTR_*)与 flag 位掩码(HSA_SVM_FLAG_*)。
HSA 属性 → thunk 属性/flag 映射表
HSA_AMD_SVM_ATTRIB_* |
转换结果 |
|---|---|
GLOBAL_FLAG=FINE_GRAINED |
`set_flags |
GLOBAL_FLAG=COARSE_GRAINED |
`clear_flags |
READ_ONLY |
set/clear HSA_SVM_FLAG_GPU_RO |
HIVE_LOCAL |
set/clear HSA_SVM_FLAG_HIVE_LOCAL |
READ_MOSTLY |
set/clear HSA_SVM_FLAG_GPU_READ_MOSTLY |
GPU_EXEC |
set/clear HSA_SVM_FLAG_GPU_EXEC |
MIGRATION_GRANULARITY |
HSA_SVM_ATTR_GRANULARITY(上限 log2=18,即 1GB) |
PREFERRED_LOCATION |
HSA_SVM_ATTR_PREFERRED_LOC(agent→node_id,null→INVALID_NODEID) |
AGENT_ACCESSIBLE(GPU) |
HSA_SVM_ATTR_ACCESS(node_id) |
AGENT_ACCESSIBLE(CPU) |
`set_flags |
AGENT_ACCESSIBLE_IN_PLACE(GPU) |
HSA_SVM_ATTR_ACCESS_IN_PLACE(node_id) |
AGENT_NO_ACCESS(GPU) |
HSA_SVM_ATTR_NO_ACCESS(node_id) |
关键代码
cpp
// 布尔类属性 → flag 位掩码
case HSA_AMD_SVM_ATTRIB_READ_MOSTLY:
if (value) set_flags |= HSA_SVM_FLAG_GPU_READ_MOSTLY;
else clear_flags |= HSA_SVM_FLAG_GPU_READ_MOSTLY;
break;
// 位置/访问类属性 → HSA_SVM_ATTRIBUTE(agent handle 转 node_id)
case HSA_AMD_SVM_ATTRIB_PREFERRED_LOCATION: {
Agent* agent = ConvertAllowNull(value);
attribs.push_back(kmtPair(HSA_SVM_ATTR_PREFERRED_LOC,
agent ? agent->node_id() : INVALID_NODEID));
break;
}
case HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE: {
Agent* agent = Convert(value);
if (agent->device_type() == Agent::kAmdCpuDevice)
set_flags |= HSA_SVM_FLAG_HOST_ACCESS; // CPU 走 flag
else
attribs.push_back(kmtPair(HSA_SVM_ATTR_ACCESS, agent->node_id())); // GPU 走 attr
break;
}
// 汇总 flag 更新
if (clear_flags) attribs.push_back(kmtPair(HSA_SVM_ATTR_CLR_FLAGS, clear_flags));
if (set_flags) attribs.push_back(kmtPair(HSA_SVM_ATTR_SET_FLAGS, set_flags));
// 页对齐后下发到 thunk
uint8_t* base = AlignDown((uint8_t*)ptr, 4096);
uint8_t* end = AlignUp((uint8_t*)ptr + size, 4096);
size_t len = end - base;
HSAKMT_STATUS error = HSAKMT_CALL(
hsaKmtSVMSetAttr(base, len, attribs.size(), &attribs[0]));
if (error != HSAKMT_STATUS_SUCCESS)
throw AMD::hsa_exception(HSA_STATUS_ERROR, "hsaKmtSVMSetAttr failed.");
关键转换点:
- agent handle → node_id :ROCr 用
Agent::node_id()把 HSA agent 句柄转换成KFD 拓扑节点 ID。 - CPU vs GPU 分流 :CPU 访问用
HSA_SVM_FLAG_HOST_ACCESS位;GPU 访问用HSA_SVM_ATTR_ACCESS[_IN_PLACE]属性携带具体 node_id。 - flag 合并 :所有布尔属性汇聚成
set_flags/clear_flags两个位掩码, 分别打包成一个HSA_SVM_ATTR_SET_FLAGS/HSA_SVM_ATTR_CLR_FLAGS属性。 - 页对齐 :
ptr向下、size向上对齐到 4KB 页边界(KFD ioctl 强制要求)。
3.7 libhsakmt(thunk)层:hsaKmtSVMSetAttr
thunk 入口(libhsakmt/src/svm.c)转调带 context 的版本,最终落到 KFD ioctl:
c
HSAKMT_STATUS HSAKMTAPI
hsaKmtSVMSetAttr(void *start_addr, HSAuint64 size, unsigned int nattr,
HSA_SVM_ATTRIBUTE *attrs) {
return hsaKmtSVMSetAttrCtx(&hsakmt_primary_kfd_ctx, start_addr, size, nattr, attrs);
}
hsaKmtSVMSetAttrCtx 有两条编译路径:
(a) 传统 KFD ioctl 路径(默认):
c
struct kfd_ioctl_svm_args *args;
args->start_addr = (uint64_t)start_addr;
args->size = size;
args->op = KFD_IOCTL_SVM_OP_SET_ATTR;
args->nattr = nattr;
memcpy(args->attrs, attrs, sizeof(*attrs) * nattr);
// node_id → gpu_id(access / preferred_loc 类属性需要)
for (i = 0; i < nattr; i++) {
if (attrs[i].type == KFD_IOCTL_SVM_ATTR_PREFERRED_LOC &&
attrs[i].value == INVALID_NODEID) {
args->attrs[i].value = KFD_IOCTL_SVM_LOCATION_UNDEFINED;
continue;
}
hsakmt_validate_nodeid(ctx, attrs[i].value, &args->attrs[i].value);
}
// 变长 ioctl:attrs 大小编码进 ioctl number 高位
r = hsakmt_ioctl(ctx->fd,
AMDKFD_IOC_SVM + (s_attr << _IOC_SIZESHIFT), args);
(b) DRM render-node 路径 (USE_DRM_AMDGPU_SVM,virtio/passthrough 场景):
hsaKmtSVMSetAttrCtx_drm 把 HSA_SVM_ATTRIBUTE 翻译成 struct drm_amdgpu_svm_attribute(AMDGPU_SVM_ATTR_*),再调用
amdgpu_svm_set_attr(deviceHandle, ...)。
注意 thunk 层又做了一次 node_id → gpu_id 的转换:ROCr 传下来的是 KFD 拓扑 node id,而 ioctl 需要的是 GPU id,由
hsakmt_validate_nodeid完成。
3.8 KFD 内核层:AMDKFD_IOC_SVM
hsakmt_ioctl 通过 ioctl(fd, AMDKFD_IOC_SVM, args) 陷入内核。KFD 驱动(kfd_ioctl_svm → svm_ioctl → svm_range_set_attr)根据 op 与属性列表,定位/切分对应的 svm_range,更新其 preferred_loc、access 位图、flags、granularity 等,并按需触发 HMM 迁移或页表更新。
4. 关键设计要点
4.1 HMM 能力检查(优雅降级)
只有 info().hmmSupported_ 为真时才真正调用 ROCr,否则打印 warning 并返回成功。这保证了在不支持 HMM/XNACK 的旧硬件上,hipMemAdvise 不会失败,只是变成 no-op。
4.2 首次分配 vs 后续更新的语义区别
SetAccessedBy 根据 first_alloc 选择不同 HSA 属性:
- 首次分配 (
first_alloc=true)→HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE
允许 page fault 触发按需迁移,并为所有可访问 GPU 开放访问。 - 后续更新 (
first_alloc=false)→HSA_AMD_SVM_ATTRIB_AGENT_ACCESSIBLE_IN_PLACE
原地访问、不触发迁移,仅针对当前 device。
注释指出:理论上 XNACK 开启时 HMM 应能自动更新页表,但当前实现仍显式为所有 device 开放访问以规避该限制。
4.3 CPU / NUMA 目标处理
use_cpu 为真时目标 agent 取 getCpuAgent(numa_id),支持把内存的 preferred location 或 accessed-by 指向指定 NUMA 节点的 CPU;否则取 getBackendDevice()(GPU agent)。
4.4 动态符号解耦
ROCR_DYN + GET_ROCR_SYMBOL 机制让 ROCclr 与 ROCr 版本解耦,新旧 ROCr 之间只要符号存在即可工作,缺失符号时可优雅处理。
5. 相关源码位置速查
| 层次 | 文件 | 关键符号 |
|---|---|---|
| HIP API | projects/clr/hipamd/src/hip_hmm.cpp |
hipMemAdvise,枚举 static_assert |
| 抽象接口 | projects/clr/rocclr/device/device.hpp |
amd::Device::SetSvmAttributes,enum MemoryAdvice |
| ROCm 实现 | projects/clr/rocclr/device/rocm/rocdevice.cpp |
SetSvmAttributesInt(advice→HSA 映射) |
| 动态包装 | projects/clr/rocclr/device/rocm/rocrctx.hpp |
Hsa::svm_attributes_set |
| 符号加载 | projects/clr/rocclr/device/rocm/rocrctx.cpp |
GET_ROCR_SYMBOL(hsa_amd_svm_attributes_set) |
| ROCr 接口 | runtime/hsa-runtime/inc/hsa_ext_amd.h |
hsa_amd_svm_attributes_set 声明 |
| ROCr 薄封装 | runtime/hsa-runtime/core/runtime/hsa_ext_amd.cpp |
hsa_amd_svm_attributes_set(转 SetSvmAttrib) |
| ROCr 核心翻译 | runtime/hsa-runtime/core/runtime/runtime.cpp |
Runtime::SetSvmAttrib(HSA→thunk 映射、node_id 转换) |
| thunk 入口 | libhsakmt/src/svm.c |
hsaKmtSVMSetAttr / hsaKmtSVMSetAttrCtx |
| thunk DRM 路径 | libhsakmt/src/svm.c |
hsaKmtSVMSetAttrCtx_drm(amdgpu_svm_set_attr) |
| thunk 接口 | libhsakmt/include/hsakmt/hsakmt.h |
hsaKmtSVMSetAttr 声明 |
| KFD ioctl | 内核 kfd_chardev.c |
AMDKFD_IOC_SVM → svm_ioctl → svm_range_set_attr |
6. 小结
hipMemAdvise 的本质是把用户对托管内存的访问/迁移建议,逐层翻译、逐层下沉:
hipMemoryAdvise → amd::MemoryAdvice → HSA_AMD_SVM_ATTRIB_*
→ HSA_SVM_ATTR_* / HSA_SVM_FLAG_* → kfd_ioctl_svm_args → KFD svm_range
整条链路有两个核心翻译点:
- ROCclr 侧
rocdevice.cpp::SetSvmAttributesInt:advice→HSA 属性映射,配合info().hmmSupported_实现跨硬件优雅降级,配合first_alloc区分首次分配与运行时调整的迁移语义。 - ROCr 侧
runtime.cpp::SetSvmAttrib:HSA 属性→thunk 属性/flag 映射,完成 agent handle→node_id 转换、CPU/GPU 访问分流、flag 位掩码合并、页对齐。
再经 libhsakmt hsaKmtSVMSetAttr(node_id→gpu_id)打包成变长 AMDKFD_IOC_SVM ioctl,最终由 KFD 更新对应 svm_range 的属性并按需触发 HMM 迁移/页表更新。
7. 设计讨论:SVM Range 是进程级资源
在调试时,你会发现在hip层是通过device调用的,但到rocr层,device信息丢了。但与SVM 的底层设计有关:
SVM Range 是进程级的资源,不是绑定于特定设备的内存。
对比
| 特性 | 传统设备内存 (hipMalloc) |
SVM Range (hipMallocManaged) |
|---|---|---|
| 归属 | 绑定特定 GPU | 进程级资源 |
| 标识 | device memory handle | 虚拟地址 (ptr, size) |
| 设备角色 | 内存拥有者 | 内存访问者 |
API 设计印证
c
hsa_amd_svm_attributes_set(
void* ptr, // 虚拟地址标识 range(主参数)
size_t size,
attrs, // 设备信息在属性值中,不是 API 主参数
attr_count
);
设备信息从"调用者身份"转换 为"属性值"(如 PREFERRED_LOCATION 的 value),因为设备是这段内存的访问者 ,不是拥有者。
这与内核 HMM 设计一致:VMA 属于进程,设备只是注册为可以访问它的实体。
初次调试这个调用栈时,会有些疑惑,感觉上下不对称。不知道大家有没有这个感觉。