libhsakmt 内存分配中的 NUMA 使用流程与实现

📅 2026/8/4 11:13:35
libhsakmt 内存分配中的 NUMA 使用流程与实现
本文分析 libhsakmtThunk在hsaKmtAllocMemory分配路径中如何使用 NUMA把系统内存绑定到离 GPU 最近的 CPU 节点以降低 GPU 访问系统内存的跨节点开销。1. 目标与动机GPU 通过 PCIe / XGMI 访问系统内存system DRAM时访问延迟与带宽取决于该内存物理落在哪个 CPU 内存控制器IMC所辖的 NUMA 节点上。落在与 GPU直连的那个 CPU 节点上最优跨 socket 则要多走一跳一致性互连延迟更高、带宽更低。因此 libhsakmt 在分配分页系统内存时会主动把这段内存用 Linuxmbind(2)绑定到 GPU 直连的 CPU NUMA 节点。一句话概括整条链路GPU node → 反查直连 CPU node → 对该段 VA 执行mbind→ 后续缺页从该 NUMA 节点取页。2. 关键前提分页系统内存才走 NUMANUMA 绑定只作用于分页的系统内存以 userptr 形式交给 KFD 的匿名页不作用于 VRAM 或 pinned 内存内存类型是否 NUMA 绑定原因分页系统内存userptr!NonPaged是页由 CPU 分配NUMA policy 决定物理落点。NonPagedpinned/ GTT / VRAM否物理落点由 KFD / 显存控制器决定不归 CPU NUMA policy 管。对应判断在fmm_allocate_host_gpusrc/fmm.c只有!mflags.ui32.NonPaged svm.userptr_for_paged_mem分支才mmap匿名页并随后mbind。3. 拓扑解析GPU → 直连 CPU 节点NUMA 目标节点不是由调用者传入的而是由 GPU 节点在拓扑中反查得到。3.1 挑选直连 CPUgpu_get_direct_link_cpu位于src/topology.c遍历该 GPU 的 IO links挑选满足条件的对端 CPU 节点for(i0;inode_props[gpu_node].node.NumIOLinks;i)if((props[i].IoLinkTypeHSA_IOLINKTYPE_PCIEXPRESS||props[i].IoLinkTypeHSA_IOLINK_TYPE_XGMI)props[i].Weight20)/* 20 是 GPU-CPU-GPU 的间接路径 */{if(!node_props[props[i].NodeTo].node.KFDGpuID)// 对端必须是 CPU 节点returnprops[i].NodeTo;}要点只认PCIe 或 XGMI直连链路Weight 20用于排除绕经另一颗 CPU/GPU的间接路径权重越大越远对端KFDGpuID 0表示它是 CPU 节点而非 GPU。3.2 校验并返回 NUMA idhsakmt_get_direct_link_cpu外层封装再确认该 CPU 节点确实挂有内存 bank否则视为无效cpu_idgpu_get_direct_link_cpu(gpu_node,topology_ctx-node_props);if(cpu_id-1)returnINVALID_NODEID;for(i0;inode_props[cpu_id].node.NumMemoryBanks;i)sizenode_props[cpu_id].mem[i].SizeInBytes;returnsize?(uint32_t)cpu_id:INVALID_NODEID;术语区分分配 API 里的PreferredNode/preferred_gpu_id指的是GPU 节点决定用哪张卡的 aperture 与 BO而这里得到的numa_node_id是由 GPU 反查出来的直连 CPU 节点二者含义不同不要混淆。4. 绑定核心bind_mem_to_numa位于src/fmm.c是唯一的 NUMA 绑定实现本质是对mbind(2)的封装。4.1 提前退出的两种情况if(mflags.ui32.NoNUMABind||numa_available()-1){if(mflags.ui32.NoSubstitute)return-EFAULT;// 要求硬绑定却无法绑 → 失败elsereturn0;// 否则静默放行让页落在默认节点}NoNUMABind调用者显式关闭 NUMA 绑定对应HsaMemFlags的同名位numa_available() -1系统无 NUMA 支持。无效节点越界 /INVALID_NODEID/ 只有一个节点也走同样的要么失败、要么放行逻辑。4.2 硬约束 vs 软偏好intmodeMPOL_F_STATIC_NODES;...mode|mflags.ui32.NoSubstitute?MPOL_BIND:MPOL_PREFERRED;rmbind(mem,SizeInBytes,mode,node_mask-maskp,num_node1,0);NoSubstitutepolicy语义置位MPOL_BIND必须落在直连节点无法满足即失败-EFAULT。不置位MPOL_PREFERRED尽量落在直连节点不行则退让到其他节点。MPOL_F_STATIC_NODES保证节点掩码不随进程 cpuset 变化被重映射绑定的是物理节点。4.3 失败处理与兼容性if(r){if(errnoEPERM){// docker seccomp 默认拦截 mbindpr_err_once(mbind is blocked by seccomp\n);return0;// 故意放行否则容器内无法分配系统内存}if(!mflags.ui32.NoSubstitute)// 非硬绑定时无内存可用也忽略return0;return-EFAULT;// 硬绑定失败才真正报错}容器兼容seccomp 拦截mbind返回EPERM时返回 0保证容器内可用尽力而为非NoSubstitute情况下即便mbind失败也当成功宁可换节点也不让分配失败。5. 分配主流程fmm_allocate_host_gpu分页系统内存路径中NUMA 绑定发生在拿到 VA、建立 BO 之前确保后续缺页从正确节点取页if(!mflags.ui32.NonPagedfmm_ctx-svm.userptr_for_paged_mem){/* 1. 保留并映射匿名页的 VA */memaperture_allocate_area_aligned(aperture,address,size,alignment);mmap(mem,MemorySizeInBytes,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED,-1,0);/* 2. 绑定到 GPU 直连的 CPU NUMA 节点 */if(bind_mem_to_numa(node_id,mem,MemorySizeInBytes,mflags))gotoout_release_area;/* 3. 大页建议 */madvise(mem,MemorySizeInBytes,advice);// 2MB 时 MADV_HUGEPAGE/* 4. 作为 userptr BO 交给 KFD */ioc_flags|KFD_IOC_ALLOC_MEM_FLAGS_USERPTR;vm_objfmm_allocate_memory_object(ctx,preferred_gpu_id,mem,size,aperture,mmap_offset,ioc_flags);}顺序很关键先mbind设策略再让内存被 touch / 建 BO这样物理页首次分配时就落到目标 NUMA 节点避免先分配后迁移的开销。5.1 udmabuf 路径的额外容量检查另一处调用在 udmabuf 分配路径src/fmm.c在绑定后还会用numa_node_size64检查目标节点剩余内存是否足够不足则告警并可回退numa_node_idhsakmt_get_direct_link_cpu(ctx,node_id);if(bind_mem_to_numa(numa_node_id,mem,size,mflags))...node_sizenuma_node_size64(numa_node_id,free_size);/* 比较 free_size 与请求 size */6. 整体流程图是否, 分页是是否否是否hsaKmtAllocMemory系统内存, HostAccessNonPaged?(pinned)走 KFD 直接分配不做 NUMA 绑定mmap 匿名页, 拿到 VAhsakmt_get_direct_link_cpuGPU node → 直连 CPU nodebind_mem_to_numaNoNUMABind /无 NUMA?NoSubstitute?返回 -EFAULT放行(默认节点)NoSubstitute?mbind MPOL_BIND(硬约束)mbind MPOL_PREFERRED(软偏好)madvise 建 userptr BO7. 关键点小结只绑分页系统内存NonPaged/GTT/VRAM 不经过 NUMA 绑定。目标节点靠拓扑反查由 GPU 节点经 IO linkPCIe/XGMIWeight20找到直连 CPU 节点而非调用者指定。强弱可调NoSubstitute决定MPOL_BIND硬约束还是MPOL_PREFERRED软偏好NoNUMABind整体关闭。顺序保证先mbind再触发缺页 / 建 BO让首次分配即落在目标节点。健壮性seccompEPERM与无内存可用时对非硬绑定路径静默放行保证容器与紧张场景下仍能分配成功。8. 相关源码位置功能文件符号挑选 GPU 直连 CPU 节点libhsakmt/src/topology.cgpu_get_direct_link_cpu校验并返回 NUMA idlibhsakmt/src/topology.chsakmt_get_direct_link_cpuNUMA 绑定核心libhsakmt/src/fmm.cbind_mem_to_numa分页系统内存分配主流程libhsakmt/src/fmm.cfmm_allocate_host_gpuaperture 释放时复位 NUMA policylibhsakmt/src/fmm.cmmap_aperture_release/reserved_aperture_release关联阅读NUMA-01 你的内存不是一整块看懂 NUMA 与机器拓扑NUMA-02一段内存到底在哪个 node用户态 NUMA 编程接口linux VMA 的 NUMA 信息使用流程从 vm_policy 到物理页落点