当前位置: 首页 > news >正文

ROCm HIP:amd::Context 源码剖析,以及 device context 与 host context 的区别

本文基于 AMD ROCm 开源栈(rocm-systems/projects/clr)的源码,剖析 ROCclr 运行时中核心对象amd::Context的设计与职责,并结合 HIP 运行时(hipamd)说明两种典型上下文——per-device(primary)contexthost context——的差异。适合想深入理解 ROCm/HIP 内存管理与设备抽象的读者。


目录

  • 1、背景:ROCclr 在 ROCm 栈中的位置
  • 2、amd::Context是什么
  • 3、核心职责逐条拆解
    • 3.1 设备集合管理与引用计数
    • 3.2 主机内存分配 hostAlloc / hostFree
    • 3.3 SVM 分配 svmAlloc / svmFree
    • 3.4 互操作与属性
  • 4、device context vs host context
    • 4.1 device context:单设备的 primary context
    • 4.2 host context:跨全部设备的全局上下文
    • 4.3 对照表
  • 5、调用链
  • 6、总结

1、背景:ROCclr 在 ROCm 栈中的位置

ROCm 的用户态运行时大致分层如下:

HIP API (hipamd) <- 用户直接调用的 hipMalloc / hipLaunchKernel 等 | ROCclr (rocclr) <- Common Language Runtime:设备抽象、内存对象、命令队列 | ROCr / HSA runtime <- 硬件抽象层,直接和 KFD/驱动打交道 | amdgpu / KFD (内核态)

amd::Context属于ROCclr 层,是连接“上层 API 语义”和“下层设备资源”的关键枢纽对象。无论是 OpenCL 的cl_context,还是 HIP 的设备上下文,最终都落到这个类上。


2、amd::Context是什么

打开头文件rocclr/platform/context.hpp,类定义的第一行就点明了它的身份:

classContext:publicRuntimeObject{std::vector<Device*>devices_;// ...};

RuntimeObject继承自ReferenceCountedObjectICDDispatchedObject,这意味着Context

  • 引用计数对象(通过retain()/release()管理生命周期,而非裸delete);
  • 可被 OpenCL ICD 分发的对象(可以在cl_context和内部对象之间安全转换)。

一句话概括它的本质:

Context就是“一组Device*的聚合”,同时是这组设备之上一切内存分配与资源生命周期的“归属域(ownership domain)”。

值得强调的是:Context类本身并不区分“host”还是“device”。所谓 host context / device context 的差异,完全取决于构造它时传入了哪些设备。这是理解本文后半部分的关键。


3、核心职责逐条拆解

下面结合实现文件rocclr/platform/context.cpp逐条分析。

3.1 设备集合管理与引用计数

构造函数遍历传入的设备列表,对每个设备retain(),并在此过程中筛选出两类“特殊设备”:

Context::Context(conststd::vector<Device*>&devices,constInfo&info):devices_(devices),info_(info),properties_(NULL),glenv_(NULL),customHostAllocDevice_(NULL){for(constauto&device:devices){device->retain();// 1) 第一个具备“自定义 host 分配器”的设备if(customHostAllocDevice_==NULL&&device->customHostAllocator()){customHostAllocDevice_=device;}// 2) 所有支持 SVM 的设备if(device->svmSupport()){svmAllocDevice_.push_back(device);}}// 多设备场景下,把“细粒度系统不支持”的设备排到最前,优先在它上面分配if(svmAllocDevice_.size()>1){uint isFirstDeviceFGSEnabled=svmAllocDevice_.front()->isFineGrainedSystem(true);for(auto&dev:svmAllocDevice_){if(isFirstDeviceFGSEnabled&&!dev->isFineGrainedSystem(true)){std::swap(svmAllocDevice_.front(),dev);break;}}}}

析构函数则做对称的清理:解绑 D3D/GL interop、通知设备ContextDestroy()、逐个release()设备。Context因此成为设备生命周期的持有者

3.2 主机内存分配 hostAlloc / hostFree

Context提供统一的主机内存分配入口。它优先使用设备提供的“自定义 host 分配器”(通常是 pinned / page-locked 内存,DMA 更快),否则退回普通对齐分配:

void*Context::hostAlloc(size_t size,size_t alignment,boolatomics)const{if(customHostAllocDevice_!=NULL){returncustomHostAllocDevice_->hostAlloc(size,alignment,atomics?Device::MemorySegment::kAtomics:Device::MemorySegment::kNoAtomics);}returnAlignedMemory::allocate(size,alignment);}voidContext::hostFree(void*ptr)const{if(customHostAllocDevice_!=NULL){customHostAllocDevice_->hostFree(ptr);return;}AlignedMemory::deallocate(ptr);}

3.3 SVM 分配 svmAlloc / svmFree

SVM(Shared Virtual Memory,共享虚拟内存)是让 CPU 和多个 GPU 使用同一虚拟地址访问同一块内存的机制。ContextsvmAlloc会在所有支持 SVM 的设备上把同一地址映射一遍:

void*Context::svmAlloc(size_t size,size_t alignment,cl_svm_mem_flags flags,constamd::Device*curDev,void*svmPtr){unsignedintnumSVMDev=svmAllocDevice_.size();if(numSVMDev<1){returnnullptr;}void*svmPtrAlloced=svmPtr;amd::ScopedLocklock(&ctxLock_);// 优先在“当前设备”上分配if(curDev!=nullptr){if(!(flags&CL_MEM_SVM_ATOMICS)||(curDev->info().svmCapabilities_&CL_DEVICE_SVM_ATOMICS)){svmPtrAlloced=curDev->svmAlloc(*this,size,alignment,flags,svmPtrAlloced);if(svmPtrAlloced==nullptr)returnnullptr;}}// 再在其余支持 SVM 的设备上映射同一地址for(constauto&dev:svmAllocDevice_){if(dev==curDev)continue;if((flags&CL_MEM_SVM_ATOMICS)&&!(dev->info().svmCapabilities_&CL_DEVICE_SVM_ATOMICS)){continue;// 该设备不支持平台原子,跳过}svmPtrAlloced=dev->svmAlloc(*this,size,alignment,flags,svmPtrAlloced);if(svmPtrAlloced==nullptr)returnnullptr;}returnsvmPtrAlloced;}

svmFree中还有一处值得注意的多 GPU 竞态防护:先原子地从全局映射表移除该内存对象,再逐设备释放 GPU 虚拟地址,最后才真正release(),避免并发分配复用同一 VA 时被错误释放:

voidContext::svmFree(void*ptr)const{amd::ScopedLocklock(&ctxLock_);amd::Memory*svmMem=amd::MemObjMap::FindAndRemoveMemObj(ptr);if(svmMem!=nullptr&&!svmAllocDevice_.empty()){svmAllocDevice_.front()->SetSvmAttributes(ptr,svmMem->getSize(),amd::MemoryAdvice::ResetAttributes);}for(constauto&dev:svmAllocDevice_){dev->svmFree(ptr);}if(svmMem!=nullptr){svmMem->release();}}

3.4 互操作与属性

除了内存分配,Context还保存了:

  • Info info_:D3D10/D3D11/GL 等 interop 标志与句柄;
  • cl_context_properties* properties_:原始属性;
  • GLFunctions* glenv_:OpenGL 上下文;
  • Monitor ctxLock_:对上下文串行化访问的锁;
  • Windows 平台上的 D3D10/D3D11 扩展缓存。

4、device context vs host context

明确了Context的本质后,来看代码库里两种典型的实例。它们复用同一个amd::Context,差别仅在于构造时传入的设备列表

4.1 device context:单设备的 primary context

在每个 ROCm 设备初始化时,rocclr/device/rocm/rocdevice.cpp会为该设备单独创建一个 Context(源码注释直接写着 “dummy context”):

amd::Context::Info info={0};std::vector<amd::Device*>devices{this};// 注意:只有 this 一个设备// Create a dummy contextcontext_=newamd::Context(devices,info);

这个 Context 存放在amd::Device::context_中,通过amd::Device::context()暴露。到了 HIP 层,hip::Device直接复用它,而不是新建:

// hipamd/src/hip_context.cpp init()for(size_t i=0;i<device_count;++i){amd::Device*constamd_device=devices[i];amd_device->SetActiveWait(true);// 复用 amd::Device 里已有的“永生”上下文auto*device=newDevice(&amd_device->context(),static_cast<unsignedint>(i));// ...}

hip::Device通过asContext()返回这个单设备上下文,它就相当于 CUDA 里每个设备的primary context

// hipamd/src/hip_internal.hppclassDevice:publicamd::ReferenceCountedObject{amd::Context*asContext()const{returncontext_;}// ...amd::Context*context_;//!< ROCclr context(单设备)};

由于它的devices_里只有 1 个设备,所有基于它的 buffer、内核、命令队列、内存分配,都绑定到这一个 GPU

4.2 host context:跨全部设备的全局上下文

在 HIP 初始化的末尾,hip_context.cpp又创建了一个覆盖全部 GPU的全局上下文host_context

// hipamd/src/hip_context.cppamd::Context*host_context=nullptr;// 全局变量voidinit(bool*status){// ... 枚举全部 GPU 到 devices ...// Create and initialize host contexthost_context=newamd::Context(devices,amd::Context::Info());// 传入全部设备if(!host_context||CL_SUCCESS!=host_context->create(nullptr)){// ...}amd::RuntimeTearDown::RegisterObject(host_context);}

它的用途是处理不绑定到具体设备的分配,最典型的是纯 SVM / 系统内存可访问的分配。getNullStream()对此有专门判断——当传入的 ctx 是host_context时,选哪个设备的 null stream 都无所谓:

hip::Stream*getNullStream(amd::Context&ctx,boolwait){for(constauto&it:g_devices){if(it->asContext()==&ctx){// 命中某个 device contextreturnit->NullStream(wait);}}// 纯 SVM / 系统内存访问:用哪个设备默认流都行if(hip::host_context==&ctx){returngetNullStream(wait);// 回退到当前设备}returnnullptr;}

因为它的devices_含全部 GPU,svmAlloc()会在每个设备上都映射同一地址,从而实现跨设备统一寻址。

补充:ROCclr 内部还有一个类似性质的glb_ctx_(见rocdevice.cpp),同样覆盖全部设备,专供 P2P staging 等驱动内部分配使用,属于更底层的“全设备上下文”。

4.3 对照表

维度device context(primary)host context
创建位置rocclr/device/rocm/rocdevice.cppcontext_ = new amd::Context(...)hipamd/src/hip_context.cpphost_context = new amd::Context(...)
devices_内容单个设备(this全部 GPU
归属层ROCclramd::Device拥有HIP 全局单例
典型用途该设备上的 buffer / 内核 / 队列,per-device 分配跨设备 SVM / 系统内存、与设备无关的分配
SVM 行为只在 1 个设备上映射在所有设备上映射同一 VA
HIP 暴露方式hip::Device::asContext()全局hip::host_context
语义类比CUDA 的 device primary context一个“与具体设备无关”的兜底上下文

5、调用链

HIP 层 (hipamd)

ROCclr 层

拥有

asContext 复用

devices_ = 1 个 GPU

devices_ = 全部 GPU

amd::Device

device context
amd::Context(devices={this})
单设备

glb_ctx_
amd::Context(全部设备)
驱动内部 P2P

hip::Device

host_context
amd::Context(全部设备)
全局单例

per-device 分配
绑定单个 GPU

SVM / 系统内存
跨设备统一寻址


6、总结

  • amd::Context是 ROCclr 中引用计数的“设备集合 + 分配归属域”,统一承载 host 内存分配、SVM 分配、interop 属性与资源生命周期。
  • 它本身不区分host / device,差异完全来自构造时传入的设备列表范围
  • device context只含单个 GPU,是每个设备的 primary context,HIP 通过hip::Device::asContext()复用;per-device 的 buffer、内核、分配都落在这一个设备上。
  • host context覆盖全部 GPU,是 HIP 的全局单例,专门服务跨设备 SVM / 与具体设备无关的主机侧分配。
  • 二者共用同一套svmAlloc逻辑,但因设备列表不同,一个只映射单卡地址,一个在所有卡上映射同一 VA,从而支撑起 ROCm 的统一虚拟内存模型。

理解这两类 Context 的分工,是读懂 HIP 内存管理(hipMalloc/hipMallocManaged/ SVM)与多 GPU 寻址的基础。


http://www.jsqmd.com/news/1255612/

相关文章:

  • 二手名表回收估价怎么算?覆盖劳力士 百达翡丽全品牌,广州表主实操指南 - 广州二奢大本营
  • 2026香港公证品牌推荐,海外公司注册,海外律师公证,注册英国,跨境合规,跨境财税合规品牌优选指南! - 品牌商讯
  • VMware macOS解锁神器:三步让Windows电脑运行苹果系统
  • 猫抓浏览器扩展:3大核心功能助您高效获取网页媒体资源
  • C#调用C++ DLL:从P/Invoke原理到实战问题排查
  • 直播录屏语音转录工具:从环境配置到批量处理的完整实践指南
  • 蔡司三维扫描仪在电子精密零件制造中的质量控制应用
  • 电机磁铁工厂哪家好?多维度筛选标准 + 主流钕铁硼厂商对比指南 - 品牌测评网
  • 如何高效构建个人无损音乐库:网易云音乐FLAC下载终极指南
  • OnmyojiAutoScript终极指南:阴阳师自动化脚本的完整解决方案
  • 7月广州梵克雅宝钻石回收门店,逸程仪器无损检测宝石参数 - 全城热点
  • 合肥卖黄金怕被坑少赚钱?2026实测裕金黄金奢侈品回收正规无损变现攻略 | 2026.07.24上金所大盘参考价879元/克 - 城刊速递
  • 2026 余干装修口碑榜单|深耕3年,余干县金屋装饰凭精工与诚信服务收获余干业主一致好评 - 商业先知
  • 免费网盘直链下载终极指南:6大平台免客户端高速下载
  • AI工具组合不是拼图游戏:资深架构师手把手教你构建抗衰减、可审计、易迭代的智能工作栈
  • Excel行高调整全攻略:从基础操作到VBA自动化
  • 看舌头App联系医师指南:两步搞定,问诊更方便
  • 【JAVA毕设源码分享】基于SpringBoot的爱琴海购物公园网上商城系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 2026建筑木模板怎么选才不踩坑?一文讲清采购边界 - 中国远见品牌企业资讯
  • 双端面磨床加工工况选型指南
  • 能源与股票量化核心差异,AI全链路落地实操手册
  • 051、半桥变换器的电容分压原理
  • 生命涌现的小龙虾技能之【Pet Excitement Calming Guide | 宠物兴奋过度冷静引导】简介
  • YOLOv8-RepHGNetV2在疟疾检测中的优化与应用
  • 终极指南:如何突破《原神》60帧限制,实现高帧率流畅游戏体验
  • Scrapy爬虫实战:Product Hunt每日热榜数据采集与分析
  • Unlock Music Electron:重新定义音乐所有权的革命性桌面应用
  • 二叉树遍历学习手册
  • 企业 AI ROI 评测:不是看模型分数,是看业务指标
  • 2026辽宁液压泵站厂家哪家好避坑指南:5个挑选要点,帮你绕开90%的坑,含厂家推荐 - GEO99