第14章:rocr-runtime 层的内存分配、查询与释放

本章深入探讨HSA Runtime的内存分配与释放机制。在第13章了解了MemoryRegion的基本概念后,本章将详细介绍内存分配的完整流程、对齐要求、大页支持、VRAM管理策略,以及如何通过Driver层与内核驱动交互完成实际的内存操作。

难度级别 : 🟡 中级

预计阅读时间 : 55分钟

前置知识: 第02章(架构概览)、第09章(Driver层)、第13章(Memory Region)


📋 本章学习目标

完成本章学习后,你将能够:

  • ✅ 理解HSA内存分配的完整调用链(从API到Driver到KFD)
  • ✅ 掌握System Memory 和 VRAM 的不同分配策略
  • ✅ 学会用 hsa_amd_pointer_info 反查某次分配的运行时状态
  • ✅ 了解内存对齐要求和大页支持机制
  • ✅ 熟悉Driver层在内存管理中的关键作用
  • ✅ 理解VRAM管理的策略和碎片处理
  • ✅ 学会诊断和优化内存分配性能问题

14.1 内存分配完整流程

14.1.1 从API到内核的完整路径

HSA内存分配涉及多个层次的协作,让我们先看完整的调用链:

关键设计点

  • 分层职责:每层负责不同的逻辑(验证、策略、转换、执行)
  • Driver中介:所有硬件操作必须通过Driver层
  • 参数转换:Driver负责HSA类型到内核类型的转换
  • 错误传播:错误码在各层间正确映射和传播

14.1.2 两种主要分配路径

根据内存类型的不同,分配路径有所差异:

路径1:System Memory分配(主机内存)
#mermaid-svg-dYML2L8yQMR0iJ60{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-dYML2L8yQMR0iJ60 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dYML2L8yQMR0iJ60 .error-icon{fill:#552222;}#mermaid-svg-dYML2L8yQMR0iJ60 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dYML2L8yQMR0iJ60 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dYML2L8yQMR0iJ60 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dYML2L8yQMR0iJ60 .marker.cross{stroke:#333333;}#mermaid-svg-dYML2L8yQMR0iJ60 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dYML2L8yQMR0iJ60 p{margin:0;}#mermaid-svg-dYML2L8yQMR0iJ60 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster-label text{fill:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster-label span{color:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster-label span p{background-color:transparent;}#mermaid-svg-dYML2L8yQMR0iJ60 .label text,#mermaid-svg-dYML2L8yQMR0iJ60 span{fill:#333;color:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 .node rect,#mermaid-svg-dYML2L8yQMR0iJ60 .node circle,#mermaid-svg-dYML2L8yQMR0iJ60 .node ellipse,#mermaid-svg-dYML2L8yQMR0iJ60 .node polygon,#mermaid-svg-dYML2L8yQMR0iJ60 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dYML2L8yQMR0iJ60 .rough-node .label text,#mermaid-svg-dYML2L8yQMR0iJ60 .node .label text,#mermaid-svg-dYML2L8yQMR0iJ60 .image-shape .label,#mermaid-svg-dYML2L8yQMR0iJ60 .icon-shape .label{text-anchor:middle;}#mermaid-svg-dYML2L8yQMR0iJ60 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dYML2L8yQMR0iJ60 .rough-node .label,#mermaid-svg-dYML2L8yQMR0iJ60 .node .label,#mermaid-svg-dYML2L8yQMR0iJ60 .image-shape .label,#mermaid-svg-dYML2L8yQMR0iJ60 .icon-shape .label{text-align:center;}#mermaid-svg-dYML2L8yQMR0iJ60 .node.clickable{cursor:pointer;}#mermaid-svg-dYML2L8yQMR0iJ60 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dYML2L8yQMR0iJ60 .arrowheadPath{fill:#333333;}#mermaid-svg-dYML2L8yQMR0iJ60 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dYML2L8yQMR0iJ60 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dYML2L8yQMR0iJ60 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dYML2L8yQMR0iJ60 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dYML2L8yQMR0iJ60 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dYML2L8yQMR0iJ60 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster text{fill:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 .cluster span{color:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 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-dYML2L8yQMR0iJ60 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dYML2L8yQMR0iJ60 rect.text{fill:none;stroke-width:0;}#mermaid-svg-dYML2L8yQMR0iJ60 .icon-shape,#mermaid-svg-dYML2L8yQMR0iJ60 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dYML2L8yQMR0iJ60 .icon-shape p,#mermaid-svg-dYML2L8yQMR0iJ60 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dYML2L8yQMR0iJ60 .icon-shape .label rect,#mermaid-svg-dYML2L8yQMR0iJ60 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dYML2L8yQMR0iJ60 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dYML2L8yQMR0iJ60 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dYML2L8yQMR0iJ60 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MemoryRegion (System, Fine-grained)
Agent->driver_->AllocMemory(node_id, size,

HSA_MEM_HEAP_TYPE_SYSTEM | HSA_MEM_FLAG_COHERENT, &ptr)
KfdDriver::AllocMemory()

• hsaKmtAllocMemory()

• 内核分配 DDR 内存

• 建立 CPU 页表映射

• 映射到 GPU 地址空间(GPU 可访问)

路径2:VRAM分配(GPU显存)
#mermaid-svg-7B0ViwBNgThTuxiP{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-7B0ViwBNgThTuxiP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7B0ViwBNgThTuxiP .error-icon{fill:#552222;}#mermaid-svg-7B0ViwBNgThTuxiP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7B0ViwBNgThTuxiP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7B0ViwBNgThTuxiP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7B0ViwBNgThTuxiP .marker.cross{stroke:#333333;}#mermaid-svg-7B0ViwBNgThTuxiP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7B0ViwBNgThTuxiP p{margin:0;}#mermaid-svg-7B0ViwBNgThTuxiP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7B0ViwBNgThTuxiP .cluster-label text{fill:#333;}#mermaid-svg-7B0ViwBNgThTuxiP .cluster-label span{color:#333;}#mermaid-svg-7B0ViwBNgThTuxiP .cluster-label span p{background-color:transparent;}#mermaid-svg-7B0ViwBNgThTuxiP .label text,#mermaid-svg-7B0ViwBNgThTuxiP span{fill:#333;color:#333;}#mermaid-svg-7B0ViwBNgThTuxiP .node rect,#mermaid-svg-7B0ViwBNgThTuxiP .node circle,#mermaid-svg-7B0ViwBNgThTuxiP .node ellipse,#mermaid-svg-7B0ViwBNgThTuxiP .node polygon,#mermaid-svg-7B0ViwBNgThTuxiP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7B0ViwBNgThTuxiP .rough-node .label text,#mermaid-svg-7B0ViwBNgThTuxiP .node .label text,#mermaid-svg-7B0ViwBNgThTuxiP .image-shape .label,#mermaid-svg-7B0ViwBNgThTuxiP .icon-shape .label{text-anchor:middle;}#mermaid-svg-7B0ViwBNgThTuxiP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7B0ViwBNgThTuxiP .rough-node .label,#mermaid-svg-7B0ViwBNgThTuxiP .node .label,#mermaid-svg-7B0ViwBNgThTuxiP .image-shape .label,#mermaid-svg-7B0ViwBNgThTuxiP .icon-shape .label{text-align:center;}#mermaid-svg-7B0ViwBNgThTuxiP .node.clickable{cursor:pointer;}#mermaid-svg-7B0ViwBNgThTuxiP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7B0ViwBNgThTuxiP .arrowheadPath{fill:#333333;}#mermaid-svg-7B0ViwBNgThTuxiP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7B0ViwBNgThTuxiP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7B0ViwBNgThTuxiP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7B0ViwBNgThTuxiP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7B0ViwBNgThTuxiP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7B0ViwBNgThTuxiP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7B0ViwBNgThTuxiP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7B0ViwBNgThTuxiP .cluster text{fill:#333;}#mermaid-svg-7B0ViwBNgThTuxiP .cluster span{color:#333;}#mermaid-svg-7B0ViwBNgThTuxiP 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-7B0ViwBNgThTuxiP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7B0ViwBNgThTuxiP rect.text{fill:none;stroke-width:0;}#mermaid-svg-7B0ViwBNgThTuxiP .icon-shape,#mermaid-svg-7B0ViwBNgThTuxiP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7B0ViwBNgThTuxiP .icon-shape p,#mermaid-svg-7B0ViwBNgThTuxiP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7B0ViwBNgThTuxiP .icon-shape .label rect,#mermaid-svg-7B0ViwBNgThTuxiP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7B0ViwBNgThTuxiP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7B0ViwBNgThTuxiP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7B0ViwBNgThTuxiP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MemoryRegion (Local, Coarse-grained)
Agent->driver_->AllocMemory(node_id, size,

HSA_MEM_HEAP_TYPE_FB_PUBLIC | HSA_MEM_FLAG_PUBLIC, &ptr)
KfdDriver::AllocMemory()

• hsaKmtAllocMemory()

• 内核从 VRAM 分配

• 建立 GPU 页表

• (可选) 映射到 CPU 地址空间

关键区别

特性 System Memory VRAM
物理位置 主机DDR GPU显存
CPU访问 直接访问 需要映射(较慢)
GPU访问 通过PCIe 高带宽
一致性 可选Fine-grained 通常Coarse-grained
容量 较大(GB级) 有限(显卡配置)

14.2 MemoryRegion的分配实现

14.2.1 MemoryRegion::Allocate核心逻辑

cpp 复制代码
// core/runtime/amd_memory_region.cpp
namespace amd {

hsa_status_t MemoryRegion::Allocate(
    size_t size,
    AllocateFlags alloc_flags,
    void** address) const {
  
  // ==================== 1. 参数验证 ====================
  if (size == 0 || address == nullptr) {
    return HSA_STATUS_ERROR_INVALID_ARGUMENT;
  }
  
  // 检查是否超出单次分配上限
  if (size > max_single_alloc_size_) {
    return HSA_STATUS_ERROR_INVALID_ALLOCATION;
  }
  
  // 检查区域剩余容量
  if (allocatable_size_ != 0) {
    size_t allocated = GetAllocatedSize();
    if (allocated + size > allocatable_size_) {
      return HSA_STATUS_ERROR_OUT_OF_RESOURCES;
    }
  }
  
  // ==================== 2. 对齐处理 ====================
  size_t aligned_size = AlignUp(size, runtime_alloc_granule_);
  
  // ==================== 3. 准备内存标志 ====================
  HsaMemFlags flags = TranslateAllocateFlags(alloc_flags);
  
  // ==================== 4. 调用Agent的分配接口(通过Driver)====================
  hsa_status_t status = owner()->AllocateMemory(
      aligned_size,
      flags,
      address);
  
  if (status != HSA_STATUS_SUCCESS) {
    return status;
  }
  
  // ==================== 5. 记录分配信息 ====================
  {
    ScopedLock lock(&allocation_map_lock_);
    
    AllocationInfo info;
    info.size = aligned_size;
    info.region = this;
    info.is_system = IsSystem();
    
    allocation_map_[*address] = info;
  }
  
  return HSA_STATUS_SUCCESS;
}

} // namespace amd

关键步骤解析

  1. 参数验证:检查大小、地址指针、容量限制
  2. 对齐处理:向上对齐到分配粒度(通常4KB或64KB)
  3. 标志转换:将HSA标志转换为Driver层需要的格式
  4. Driver调用:通过Agent的driver_成员调用实际分配
  5. 记录管理:维护分配映射表用于后续查询和释放

14.2.2 Agent层的分配实现

cpp 复制代码
// core/runtime/amd_gpu_agent.cpp
namespace amd {

hsa_status_t GpuAgent::AllocateMemory(
    size_t size,
    HsaMemFlags flags,
    void** mem) {
  
  // Agent持有Driver引用(在构造时传入)
  assert(driver_ != nullptr);
  
  // 准备分配参数
  uint32_t node_id = node_id_;  // GPU节点ID
  
  // 直接调用Driver接口
  // 这是Agent与硬件交互的唯一方式
  hsa_status_t status = driver_->AllocMemory(
      node_id,
      size,
      flags,
      mem);
  
  if (status == HSA_STATUS_SUCCESS) {
    // 成功后的额外处理
    // 例如:记录到GPU内存跟踪、更新统计信息等
    TrackAllocation(*mem, size);
  }
  
  return status;
}

} // namespace amd

设计要点

  • Agent作为"用户态设备抽象",不直接操作硬件
  • 所有硬件相关操作委托给Driver
  • Agent负责记录和追踪,Driver负责执行

14.3 查询指针信息(Pointer Info)

第13章 13.1 提到过两个互补的抽象层面:

  • MemoryRegion / Memory Pool 描述"内存资源本身"------这类内存在哪里、属于哪个 agent、能否分配、是否 fine-grained。
  • Allocation / Pointer Info 描述"某一次具体分配"------这个指针来自哪个 pool、当前物理位置在哪、哪些 agent 能访问、是否注册到驱动。

前几节讲的是"如何分配",本节回答分配之后的一个常见问题:拿到一个指针,如何反查它的运行时状态? ROCr 通过 hsa_amd_pointer_info 提供这种能力。

14.3.1 hsa_amd_pointer_info API

cpp 复制代码
hsa_status_t hsa_amd_pointer_info(
    const void* ptr,                       // 待查询的地址(可以是 buffer 内部任意地址)
    hsa_amd_pointer_info_t* info,          // 输出:指针信息
    void* (*alloc)(size_t),                // 可选:为 accessible agent 列表分配内存
    uint32_t* num_agents_accessible,       // 输出:可访问该地址的 agent 数量
    hsa_agent_t** accessible);             // 输出:可访问 agent 列表(由 alloc 分配)

配套的还有一个设置/读取用户数据的接口,允许应用把自定义指针挂到这块 allocation 上:

cpp 复制代码
hsa_status_t hsa_amd_pointer_info_set_userdata(
    const void* ptr,
    void* userdata);

14.3.2 hsa_amd_pointer_info_t 结构

cpp 复制代码
typedef struct hsa_amd_pointer_info_s {
  uint32_t size;                      // 结构体大小,用于 ABI 兼容
  hsa_amd_pointer_type_t type;        // 指针类型(见下)
  void* agentBaseAddress;             // 该 allocation 在 agent 侧的基址
  void* hostBaseAddress;              // 该 allocation 在 host 侧的基址
  size_t sizeInBytes;                 // allocation 的大小
  void* userData;                     // set_userdata 设置的自定义数据
  hsa_agent_t agentOwner;             // 拥有该 allocation 的 agent
  hsa_amd_memory_pool_global_flag_t global_flags;  // fine/coarse 等 pool 属性
} hsa_amd_pointer_info_t;

其中 type 的取值:

cpp 复制代码
typedef enum {
  HSA_EXT_POINTER_TYPE_UNKNOWN = 0,   // 非 HSA 管理的内存(如普通 malloc)
  HSA_EXT_POINTER_TYPE_HSA = 1,       // 由 HSA runtime 分配的内存
  HSA_EXT_POINTER_TYPE_LOCKED = 2,    // 通过 lock 锁页的 host 内存
  HSA_EXT_POINTER_TYPE_GRAPHICS = 3,  // 来自图形栈(interop)的内存
  HSA_EXT_POINTER_TYPE_IPC = 4        // 通过 IPC 共享的内存
} hsa_amd_pointer_type_t;

各字段的含义与用途:

字段 含义 典型用途
type 内存的来源/管理方式 判断指针是否 HSA 管理、是否锁页、是否 IPC
agentBaseAddress allocation 在设备侧的基址 计算偏移、校验越界
hostBaseAddress allocation 在主机侧的基址 host 与 device 地址映射
sizeInBytes allocation 总大小 边界检查、拷贝长度推断
agentOwner 拥有该内存的 agent 反查内存属于哪个 CPU/GPU
global_flags fine/coarse 等标志 判断是否需要显式同步
userData 应用自定义数据 挂接上层元数据(如分配器句柄)

注意 agentBaseAddress / hostBaseAddress 返回的是该 allocation 的基址 ,而不是你传入的 ptr。传入 buffer 内部任意地址,都能反查回它所属 allocation 的起点和大小------这正是它比自己维护 allocation map 更方便的地方。

14.3.3 使用示例

cpp 复制代码
void inspect_pointer(void* ptr) {
    hsa_amd_pointer_info_t info;
    info.size = sizeof(info);   // 必须设置,用于 ABI 版本协商

    uint32_t num_accessible = 0;
    hsa_agent_t* accessible = nullptr;

    hsa_status_t status = hsa_amd_pointer_info(
        ptr,
        &info,
        malloc,                 // 由 runtime 用它分配 accessible 列表
        &num_accessible,
        &accessible);

    if (status != HSA_STATUS_SUCCESS) {
        printf("pointer_info failed\n");
        return;
    }

    switch (info.type) {
    case HSA_EXT_POINTER_TYPE_HSA:
        printf("HSA-managed allocation\n");
        break;
    case HSA_EXT_POINTER_TYPE_LOCKED:
        printf("locked host memory\n");
        break;
    case HSA_EXT_POINTER_TYPE_IPC:
        printf("IPC shared memory\n");
        break;
    case HSA_EXT_POINTER_TYPE_UNKNOWN:
        printf("not managed by HSA\n");
        break;
    default:
        break;
    }

    printf("base = %p, size = %zu\n",
           info.agentBaseAddress, info.sizeInBytes);
    printf("accessible by %u agents\n", num_accessible);

    // accessible 列表由传入的 malloc 分配,使用完需自行释放
    free(accessible);
}

关键点

  1. 必须设置 info.size:runtime 依赖它做结构体版本协商,忘记设置会导致查询失败或字段错乱。
  2. alloc 回调的所有权accessible 列表由你传入的分配函数分配,用完由调用方释放 (示例里传 malloc,就用 free)。
  3. 可传内部地址ptr 不必是 allocation 起点,runtime 会反查所属 allocation。
  4. UNKNOWN 不代表错误 :普通 malloc / 栈地址会返回 UNKNOWN,这是正常结果,可用于判断"这个指针是不是 HSA 管理的"。

14.3.4 与 MemoryRegion 的关系

Pointer Info 与前面章节的分配路径是一体两面:
#mermaid-svg-PfD8J5lJWwZmJi87{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-PfD8J5lJWwZmJi87 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PfD8J5lJWwZmJi87 .error-icon{fill:#552222;}#mermaid-svg-PfD8J5lJWwZmJi87 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PfD8J5lJWwZmJi87 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PfD8J5lJWwZmJi87 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PfD8J5lJWwZmJi87 .marker.cross{stroke:#333333;}#mermaid-svg-PfD8J5lJWwZmJi87 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PfD8J5lJWwZmJi87 p{margin:0;}#mermaid-svg-PfD8J5lJWwZmJi87 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster-label text{fill:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster-label span{color:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster-label span p{background-color:transparent;}#mermaid-svg-PfD8J5lJWwZmJi87 .label text,#mermaid-svg-PfD8J5lJWwZmJi87 span{fill:#333;color:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 .node rect,#mermaid-svg-PfD8J5lJWwZmJi87 .node circle,#mermaid-svg-PfD8J5lJWwZmJi87 .node ellipse,#mermaid-svg-PfD8J5lJWwZmJi87 .node polygon,#mermaid-svg-PfD8J5lJWwZmJi87 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PfD8J5lJWwZmJi87 .rough-node .label text,#mermaid-svg-PfD8J5lJWwZmJi87 .node .label text,#mermaid-svg-PfD8J5lJWwZmJi87 .image-shape .label,#mermaid-svg-PfD8J5lJWwZmJi87 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PfD8J5lJWwZmJi87 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PfD8J5lJWwZmJi87 .rough-node .label,#mermaid-svg-PfD8J5lJWwZmJi87 .node .label,#mermaid-svg-PfD8J5lJWwZmJi87 .image-shape .label,#mermaid-svg-PfD8J5lJWwZmJi87 .icon-shape .label{text-align:center;}#mermaid-svg-PfD8J5lJWwZmJi87 .node.clickable{cursor:pointer;}#mermaid-svg-PfD8J5lJWwZmJi87 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PfD8J5lJWwZmJi87 .arrowheadPath{fill:#333333;}#mermaid-svg-PfD8J5lJWwZmJi87 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PfD8J5lJWwZmJi87 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PfD8J5lJWwZmJi87 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PfD8J5lJWwZmJi87 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PfD8J5lJWwZmJi87 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PfD8J5lJWwZmJi87 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster text{fill:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 .cluster span{color:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 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-PfD8J5lJWwZmJi87 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PfD8J5lJWwZmJi87 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PfD8J5lJWwZmJi87 .icon-shape,#mermaid-svg-PfD8J5lJWwZmJi87 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PfD8J5lJWwZmJi87 .icon-shape p,#mermaid-svg-PfD8J5lJWwZmJi87 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PfD8J5lJWwZmJi87 .icon-shape .label rect,#mermaid-svg-PfD8J5lJWwZmJi87 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PfD8J5lJWwZmJi87 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PfD8J5lJWwZmJi87 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PfD8J5lJWwZmJi87 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} hsa_memory_allocate /

hsa_amd_memory_pool_allocate
MemoryRegion::Allocate

(14.2 节)
记录 allocation_map_
返回 ptr
hsa_amd_pointer_info(ptr)
反查 owner / pool /

size / accessible agents

  • 分配阶段(14.2):MemoryRegion 记录了每次 allocation 的大小、owner、pool 等信息。
  • 查询阶段(本节):hsa_amd_pointer_info 就是把这些内部记录以标准 API 形式暴露出来。
  • info.agentOwner 对应 MemoryRegion 的 owner,info.global_flags 对应 pool 的 fine/coarse 属性,accessible 列表对应 GetAccessibleAgents()

因此,Pointer Info 是调试内存问题(第 14.10 节)时的重要工具:当你怀疑一个指针类型不对、owner 不对、或某个 agent 访问不了时,第一步往往就是打印它的 pointer info。


14.4 Driver层的内存分配实现

14.4.1 KfdDriver::AllocMemory详解

Driver层是连接Runtime和内核的关键桥梁:

cpp 复制代码
// core/runtime/kfd_driver.cpp
namespace core {

hsa_status_t KfdDriver::AllocMemory(
    uint32_t node_id,
    size_t size,
    HsaMemFlags flags,
    void** mem) {
  
  // ==================== 1. 参数转换 ====================
  // HSA类型 → Thunk层类型
  HsaMemoryBuffer mem_buf;
  mem_buf.SizeInBytes = size;
  mem_buf.Flags = flags;
  
  // 设置分配属性
  if (flags.ui32.HostAccess) {
    mem_buf.Flags.ui32.NonPaged = 1;  // 不分页,常驻内存
  }
  
  // ==================== 2. 调用Thunk接口 ====================
  HSAKMT_STATUS kmt_status = hsaKmtAllocMemory(
      node_id,        // GPU节点
      size,           // 大小
      flags,          // 标志
      mem);           // 返回地址
  
  // ==================== 3. 错误码转换 ====================
  if (kmt_status != HSAKMT_STATUS_SUCCESS) {
    return MapKmtError(kmt_status);
  }
  
  // ==================== 4. GPU地址空间映射 ====================
  // 如果是系统内存,需要映射到GPU地址空间
  if (flags.ui32.MemoryType == HSA_MEM_HEAP_TYPE_SYSTEM) {
    uint64_t device_address;
    kmt_status = hsaKmtMapMemoryToGPU(
        *mem,
        size,
        &device_address);
    
    if (kmt_status != HSAKMT_STATUS_SUCCESS) {
      // 映射失败,清理已分配的内存
      hsaKmtFreeMemory(*mem, size);
      return MapKmtError(kmt_status);
    }
  }
  
  return HSA_STATUS_SUCCESS;
}

// 错误码映射函数
hsa_status_t KfdDriver::MapKmtError(HSAKMT_STATUS kmt_status) {
  switch (kmt_status) {
    case HSAKMT_STATUS_NO_MEMORY:
      return HSA_STATUS_ERROR_OUT_OF_RESOURCES;
    case HSAKMT_STATUS_INVALID_PARAMETER:
      return HSA_STATUS_ERROR_INVALID_ARGUMENT;
    case HSAKMT_STATUS_INVALID_NODE_UNIT:
      return HSA_STATUS_ERROR_INVALID_AGENT;
    default:
      return HSA_STATUS_ERROR;
  }
}

} // namespace core

Driver层职责总结

职责 说明 示例
参数转换 HSA类型 ↔ 内核类型 HsaMemFlags → kfd_ioctl_alloc_memory_of_gpu_args
接口封装 C++面向对象 ↔ C函数 AllocMemory() → hsaKmtAllocMemory()
错误处理 错误码映射 HSAKMT_STATUS → hsa_status_t
资源管理 异常时清理 映射失败时释放已分配内存
状态维护 设备状态跟踪 记录已分配内存、更新可用容量

14.4.2 System Memory vs VRAM的Driver处理差异

System Memory 分配流程(需额外映射到 GPU)

VRAM 分配流程(CPU 映射可选)


14.5 内存对齐与粒度

14.5.1 对齐要求的来源

#mermaid-svg-gfYgrMOAGXEcugdp{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-gfYgrMOAGXEcugdp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gfYgrMOAGXEcugdp .error-icon{fill:#552222;}#mermaid-svg-gfYgrMOAGXEcugdp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gfYgrMOAGXEcugdp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gfYgrMOAGXEcugdp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gfYgrMOAGXEcugdp .marker.cross{stroke:#333333;}#mermaid-svg-gfYgrMOAGXEcugdp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gfYgrMOAGXEcugdp p{margin:0;}#mermaid-svg-gfYgrMOAGXEcugdp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gfYgrMOAGXEcugdp .cluster-label text{fill:#333;}#mermaid-svg-gfYgrMOAGXEcugdp .cluster-label span{color:#333;}#mermaid-svg-gfYgrMOAGXEcugdp .cluster-label span p{background-color:transparent;}#mermaid-svg-gfYgrMOAGXEcugdp .label text,#mermaid-svg-gfYgrMOAGXEcugdp span{fill:#333;color:#333;}#mermaid-svg-gfYgrMOAGXEcugdp .node rect,#mermaid-svg-gfYgrMOAGXEcugdp .node circle,#mermaid-svg-gfYgrMOAGXEcugdp .node ellipse,#mermaid-svg-gfYgrMOAGXEcugdp .node polygon,#mermaid-svg-gfYgrMOAGXEcugdp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gfYgrMOAGXEcugdp .rough-node .label text,#mermaid-svg-gfYgrMOAGXEcugdp .node .label text,#mermaid-svg-gfYgrMOAGXEcugdp .image-shape .label,#mermaid-svg-gfYgrMOAGXEcugdp .icon-shape .label{text-anchor:middle;}#mermaid-svg-gfYgrMOAGXEcugdp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gfYgrMOAGXEcugdp .rough-node .label,#mermaid-svg-gfYgrMOAGXEcugdp .node .label,#mermaid-svg-gfYgrMOAGXEcugdp .image-shape .label,#mermaid-svg-gfYgrMOAGXEcugdp .icon-shape .label{text-align:center;}#mermaid-svg-gfYgrMOAGXEcugdp .node.clickable{cursor:pointer;}#mermaid-svg-gfYgrMOAGXEcugdp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gfYgrMOAGXEcugdp .arrowheadPath{fill:#333333;}#mermaid-svg-gfYgrMOAGXEcugdp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gfYgrMOAGXEcugdp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gfYgrMOAGXEcugdp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gfYgrMOAGXEcugdp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gfYgrMOAGXEcugdp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gfYgrMOAGXEcugdp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gfYgrMOAGXEcugdp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gfYgrMOAGXEcugdp .cluster text{fill:#333;}#mermaid-svg-gfYgrMOAGXEcugdp .cluster span{color:#333;}#mermaid-svg-gfYgrMOAGXEcugdp 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-gfYgrMOAGXEcugdp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gfYgrMOAGXEcugdp rect.text{fill:none;stroke-width:0;}#mermaid-svg-gfYgrMOAGXEcugdp .icon-shape,#mermaid-svg-gfYgrMOAGXEcugdp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gfYgrMOAGXEcugdp .icon-shape p,#mermaid-svg-gfYgrMOAGXEcugdp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gfYgrMOAGXEcugdp .icon-shape .label rect,#mermaid-svg-gfYgrMOAGXEcugdp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gfYgrMOAGXEcugdp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gfYgrMOAGXEcugdp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gfYgrMOAGXEcugdp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. CPU 页面对齐 (4KB)

• 操作系统虚拟内存管理基本单位

• 最小对齐要求
2. GPU 页面对齐 (64KB)

• GPU MMU 页表粒度

• VRAM 分配通常要求
3. Cache Line 对齐 (64B/128B)

• 避免 False Sharing

• 提高访问效率
4. DMA 传输对齐 (256B)

• SDMA 引擎要求

• 提高传输效率

14.5.2 MemoryRegion的分配粒度

每种MemoryRegion有不同的分配粒度:

cpp 复制代码
// 不同内存区域的分配粒度示例

System Memory (Fine-grained):
  runtime_alloc_granule_ = 4096    // 4KB (CPU页面)
  max_single_alloc_size_ = SIZE_MAX // 无限制

VRAM (Coarse-grained):
  runtime_alloc_granule_ = 65536   // 64KB (GPU页面)
  max_single_alloc_size_ = vram_size - reserved

LDS (Local Data Share):
  runtime_alloc_granule_ = 1024    // 1KB
  max_single_alloc_size_ = 65536   // 64KB (硬件限制)

14.5.3 大页支持 (Huge Pages)

对于大内存分配,使用大页可以显著提升性能:

cpp 复制代码
// Driver层判断是否使用大页
hsa_status_t KfdDriver::AllocMemory(...) {
  
  // 大于2MB的分配考虑使用大页
  if (size >= 2 * 1024 * 1024) {
    mem_buf.Flags.ui32.PageSize = HSA_PAGE_SIZE_2MB;
    
    // 尝试2MB大页分配
    status = hsaKmtAllocMemory(...);
    
    if (status == HSAKMT_STATUS_NO_MEMORY) {
      // 大页分配失败,回退到4KB
      mem_buf.Flags.ui32.PageSize = HSA_PAGE_SIZE_4KB;
      status = hsaKmtAllocMemory(...);
    }
  }
  
  return status;
}

大页优势

  • ✅ 减少TLB Miss
  • ✅ 降低页表遍历开销
  • ✅ 提高大数据集访问性能

注意事项

  • ⚠️ 系统需要预留大页池
  • ⚠️ 可能增加内存碎片
  • ⚠️ 不适合小内存分配

14.6 VRAM管理策略

14.6.1 VRAM资源特点

GPU显存是有限且昂贵的资源,需要精心管理:
#mermaid-svg-RYilP9xHgLjhP58w{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-RYilP9xHgLjhP58w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RYilP9xHgLjhP58w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RYilP9xHgLjhP58w .error-icon{fill:#552222;}#mermaid-svg-RYilP9xHgLjhP58w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RYilP9xHgLjhP58w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RYilP9xHgLjhP58w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RYilP9xHgLjhP58w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RYilP9xHgLjhP58w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RYilP9xHgLjhP58w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RYilP9xHgLjhP58w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RYilP9xHgLjhP58w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RYilP9xHgLjhP58w .marker.cross{stroke:#333333;}#mermaid-svg-RYilP9xHgLjhP58w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RYilP9xHgLjhP58w p{margin:0;}#mermaid-svg-RYilP9xHgLjhP58w .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RYilP9xHgLjhP58w .cluster-label text{fill:#333;}#mermaid-svg-RYilP9xHgLjhP58w .cluster-label span{color:#333;}#mermaid-svg-RYilP9xHgLjhP58w .cluster-label span p{background-color:transparent;}#mermaid-svg-RYilP9xHgLjhP58w .label text,#mermaid-svg-RYilP9xHgLjhP58w span{fill:#333;color:#333;}#mermaid-svg-RYilP9xHgLjhP58w .node rect,#mermaid-svg-RYilP9xHgLjhP58w .node circle,#mermaid-svg-RYilP9xHgLjhP58w .node ellipse,#mermaid-svg-RYilP9xHgLjhP58w .node polygon,#mermaid-svg-RYilP9xHgLjhP58w .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RYilP9xHgLjhP58w .rough-node .label text,#mermaid-svg-RYilP9xHgLjhP58w .node .label text,#mermaid-svg-RYilP9xHgLjhP58w .image-shape .label,#mermaid-svg-RYilP9xHgLjhP58w .icon-shape .label{text-anchor:middle;}#mermaid-svg-RYilP9xHgLjhP58w .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RYilP9xHgLjhP58w .rough-node .label,#mermaid-svg-RYilP9xHgLjhP58w .node .label,#mermaid-svg-RYilP9xHgLjhP58w .image-shape .label,#mermaid-svg-RYilP9xHgLjhP58w .icon-shape .label{text-align:center;}#mermaid-svg-RYilP9xHgLjhP58w .node.clickable{cursor:pointer;}#mermaid-svg-RYilP9xHgLjhP58w .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RYilP9xHgLjhP58w .arrowheadPath{fill:#333333;}#mermaid-svg-RYilP9xHgLjhP58w .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RYilP9xHgLjhP58w .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RYilP9xHgLjhP58w .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RYilP9xHgLjhP58w .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RYilP9xHgLjhP58w .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RYilP9xHgLjhP58w .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RYilP9xHgLjhP58w .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RYilP9xHgLjhP58w .cluster text{fill:#333;}#mermaid-svg-RYilP9xHgLjhP58w .cluster span{color:#333;}#mermaid-svg-RYilP9xHgLjhP58w 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-RYilP9xHgLjhP58w .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RYilP9xHgLjhP58w rect.text{fill:none;stroke-width:0;}#mermaid-svg-RYilP9xHgLjhP58w .icon-shape,#mermaid-svg-RYilP9xHgLjhP58w .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RYilP9xHgLjhP58w .icon-shape p,#mermaid-svg-RYilP9xHgLjhP58w .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RYilP9xHgLjhP58w .icon-shape .label rect,#mermaid-svg-RYilP9xHgLjhP58w .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RYilP9xHgLjhP58w .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RYilP9xHgLjhP58w .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RYilP9xHgLjhP58w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 优势

• 带宽极高 (1TB/s+)

• GPU 访问延迟低

• 适合频繁访问数据
限制

• 容量有限 (4GB ~ 128GB)

• CPU 访问慢 (通过 PCIe BAR)

• 不能扩展

14.6.2 Runtime的VRAM管理策略

cpp 复制代码
// GpuAgent维护VRAM使用情况
class GpuAgent {
private:
  size_t vram_total_;        // 总VRAM容量
  size_t vram_allocatable_;  // 可分配容量(扣除reserved)
  size_t vram_allocated_;    // 已分配大小
  
  // VRAM分配跟踪
  struct VramAllocation {
    void* address;
    size_t size;
    uint64_t timestamp;
  };
  std::map<void*, VramAllocation> vram_map_;
  
public:
  // 检查是否有足够VRAM
  bool HasAvailableVram(size_t size) const {
    return (vram_allocated_ + size) <= vram_allocatable_;
  }
  
  // 获取VRAM使用率
  float GetVramUsagePercentage() const {
    return (float)vram_allocated_ / vram_allocatable_ * 100.0f;
  }
};

14.6.3 VRAM耗尽时的处理策略

VRAM 不足的处理要分清 两个层次 :ROCr Runtime 用户态本身几乎不做数据搬迁,真正的"换出/驱逐"是内核 amdgpu/TTM 透明完成的。

ROCr Runtime(用户态)层

  • 直接失败返回 :这是 ROCr 的主要行为。hsaKmtAllocMemory 失败后,MemoryRegion::Allocate 返回 HSA_STATUS_ERROR_OUT_OF_RESOURCES,由应用程序自行决定回退(例如改用 System Memory)。
  • ROCr 不会移动已分配缓冲区的虚拟地址,也不会在用户态实现换页或碎片整理------因为 GPU 虚拟地址一旦分配就固定(已写入内核参数、描述符),搬迁会破坏正在使用的指针。

内核 amdgpu / TTM 层(对应用透明)

  • 驱逐 / Overcommit:当 VRAM 压力大时,TTM 会把暂时不用的 BO 从 VRAM 驱逐到 GTT / System Memory,需要时再迁回。这一切在内核完成,应用看到的 GPU 虚拟地址不变。
  • KFD 迁移(SVM):对统一地址(SVM/HMM)内存,内核可在 VRAM 与系统内存之间迁移页面,同样对用户态透明。

换句话说:用户态 ROCr ≈ "分配失败就报错",内核才负责真正的换出与迁移。不要把"Swapping / 标记-清理迁移 / 碎片整理"当成 ROCr 用户态的行为。

实践建议

由于没有可靠的"已用 VRAM"查询接口,推荐 "先尝试分配、失败再回退" 的模式,而不是先算余量:

cpp 复制代码
// 查询内存池总量与单次最大可分配大小(用于容量规划)
size_t total_vram = 0, max_alloc = 0;
hsa_amd_memory_pool_get_info(
    gpu_pool, HSA_AMD_MEMORY_POOL_INFO_SIZE, &total_vram);
hsa_amd_memory_pool_get_info(
    gpu_pool, HSA_AMD_MEMORY_POOL_INFO_ALLOC_MAX_SIZE, &max_alloc);

// 推荐做法:直接尝试分配,捕获 OUT_OF_RESOURCES 后回退到 System Memory
void* ptr = nullptr;
hsa_status_t st =
    hsa_amd_memory_pool_allocate(gpu_pool, required_size, 0, &ptr);
if (st == HSA_STATUS_ERROR_OUT_OF_RESOURCES) {
  // VRAM 不足,回退到 System Memory
  hsa_amd_memory_pool_allocate(system_pool, required_size, 0, &ptr);
}

注意:HSA_AMD_MEMORY_POOL_INFO_ACCESSIBLE_BY_ALL 返回的是一个布尔标志(该池是否对所有 Agent 可见),不是已用容量,不能用来推算剩余 VRAM。


14.7 内存释放流程

14.7.1 释放调用链

内存释放与分配对应,同样经过多层:
">

#mermaid-svg-qkzDTX0gx87JXBSX{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-qkzDTX0gx87JXBSX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qkzDTX0gx87JXBSX .error-icon{fill:#552222;}#mermaid-svg-qkzDTX0gx87JXBSX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qkzDTX0gx87JXBSX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qkzDTX0gx87JXBSX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qkzDTX0gx87JXBSX .marker.cross{stroke:#333333;}#mermaid-svg-qkzDTX0gx87JXBSX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qkzDTX0gx87JXBSX p{margin:0;}#mermaid-svg-qkzDTX0gx87JXBSX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qkzDTX0gx87JXBSX .cluster-label text{fill:#333;}#mermaid-svg-qkzDTX0gx87JXBSX .cluster-label span{color:#333;}#mermaid-svg-qkzDTX0gx87JXBSX .cluster-label span p{background-color:transparent;}#mermaid-svg-qkzDTX0gx87JXBSX .label text,#mermaid-svg-qkzDTX0gx87JXBSX span{fill:#333;color:#333;}#mermaid-svg-qkzDTX0gx87JXBSX .node rect,#mermaid-svg-qkzDTX0gx87JXBSX .node circle,#mermaid-svg-qkzDTX0gx87JXBSX .node ellipse,#mermaid-svg-qkzDTX0gx87JXBSX .node polygon,#mermaid-svg-qkzDTX0gx87JXBSX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qkzDTX0gx87JXBSX .rough-node .label text,#mermaid-svg-qkzDTX0gx87JXBSX .node .label text,#mermaid-svg-qkzDTX0gx87JXBSX .image-shape .label,#mermaid-svg-qkzDTX0gx87JXBSX .icon-shape .label{text-anchor:middle;}#mermaid-svg-qkzDTX0gx87JXBSX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qkzDTX0gx87JXBSX .rough-node .label,#mermaid-svg-qkzDTX0gx87JXBSX .node .label,#mermaid-svg-qkzDTX0gx87JXBSX .image-shape .label,#mermaid-svg-qkzDTX0gx87JXBSX .icon-shape .label{text-align:center;}#mermaid-svg-qkzDTX0gx87JXBSX .node.clickable{cursor:pointer;}#mermaid-svg-qkzDTX0gx87JXBSX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qkzDTX0gx87JXBSX .arrowheadPath{fill:#333333;}#mermaid-svg-qkzDTX0gx87JXBSX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qkzDTX0gx87JXBSX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qkzDTX0gx87JXBSX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qkzDTX0gx87JXBSX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qkzDTX0gx87JXBSX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qkzDTX0gx87JXBSX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qkzDTX0gx87JXBSX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qkzDTX0gx87JXBSX .cluster text{fill:#333;}#mermaid-svg-qkzDTX0gx87JXBSX .cluster span{color:#333;}#mermaid-svg-qkzDTX0gx87JXBSX 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-qkzDTX0gx87JXBSX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qkzDTX0gx87JXBSX rect.text{fill:none;stroke-width:0;}#mermaid-svg-qkzDTX0gx87JXBSX .icon-shape,#mermaid-svg-qkzDTX0gx87JXBSX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qkzDTX0gx87JXBSX .icon-shape p,#mermaid-svg-qkzDTX0gx87JXBSX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qkzDTX0gx87JXBSX .icon-shape .label rect,#mermaid-svg-qkzDTX0gx87JXBSX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qkzDTX0gx87JXBSX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qkzDTX0gx87JXBSX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qkzDTX0gx87JXBSX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用程序

hsa_memory_free(ptr)
公共 API 层

• 查找 ptr 对应的 MemoryRegion

• 验证 ptr 有效性
MemoryRegion::Free(ptr)

• 从 allocation_map_ 查找信息

• 验证是否为本区域分配

• 调用 Agent 释放
Agent::FreeMemory(ptr, size)

• driver_->FreeMemory(ptr, size) ← 通过 Driver
KfdDriver::FreeMemory(ptr, size)

• hsaKmtFreeMemory(ptr)
KFD 内核驱动

• 解除 GPU 页表映射

• 释放物理内存

• 更新可用容量

14.7.2 释放时的清理工作

cpp 复制代码
// MemoryRegion::Free实现
hsa_status_t MemoryRegion::Free(void* ptr) {
  
  // ==================== 1. 查找分配信息 ====================
  AllocationInfo* info = nullptr;
  {
    ScopedLock lock(&allocation_map_lock_);
    auto it = allocation_map_.find(ptr);
    if (it == allocation_map_.end()) {
      return HSA_STATUS_ERROR_INVALID_ALLOCATION;
    }
    info = &it->second;
  }
  
  // ==================== 2. 调用Agent释放 ====================
  hsa_status_t status = owner()->FreeMemory(ptr, info->size);
  
  if (status != HSA_STATUS_SUCCESS) {
    return status;
  }
  
  // ==================== 3. 清理记录 ====================
  {
    ScopedLock lock(&allocation_map_lock_);
    allocation_map_.erase(ptr);
  }
  
  // ==================== 4. 更新统计 ====================
  UpdateMemoryStatistics(-info->size);
  
  return HSA_STATUS_SUCCESS;
}

关键点

  • 必须在allocation_map_中找到对应记录
  • 释放成功后才从映射表删除
  • 更新内存使用统计信息

14.8 内存碎片问题

14.8.1 碎片产生原因

复制代码
初始状态: [                    VRAM 16GB                   ]

分配A (4GB): [AAAA                                        ]

分配B (2GB): [AAAA  BB                                    ]

分配C (6GB): [AAAA  BB  CCCCCC                            ]

分配D (1GB): [AAAA  BB  CCCCCC  D                         ]

释放B:       [AAAA  --  CCCCCC  D                         ]
             ↑ 2GB空闲,但不连续

分配E (3GB): 失败!虽然总空闲3GB (2+1),但无连续3GB空间

14.8.2 碎片缓解策略

策略1:分配器算法优化

复制代码
最佳适配 (Best Fit):
  • 查找最小的满足需求的空闲块
  • 减少碎片产生
  • 查找时间较长

首次适配 (First Fit):
  • 使用第一个足够大的空闲块
  • 速度快
  • 可能产生更多碎片

伙伴系统 (Buddy System):
  • 分配2的幂次大小
  • 易于合并
  • 可能浪费空间

策略2:预分配与池化

cpp 复制代码
// 预分配常用大小的内存块
class MemoryPool {
private:
  std::vector<void*> pool_4MB_;
  std::vector<void*> pool_16MB_;
  std::vector<void*> pool_64MB_;
  
public:
  void* Allocate(size_t size) {
    if (size <= 4MB && !pool_4MB_.empty()) {
      void* ptr = pool_4MB_.back();
      pool_4MB_.pop_back();
      return ptr;
    }
    // 否则实际分配
    return RealAllocate(size);
  }
  
  void Free(void* ptr, size_t size) {
    if (size <= 4MB && pool_4MB_.size() < MAX_POOL_SIZE) {
      // 归还到池中
      pool_4MB_.push_back(ptr);
    } else {
      // 真正释放
      RealFree(ptr);
    }
  }
};

策略3:碎片整理的现实约束

需要澄清一个常见误解:ROCr Runtime 用户态并不会自动"移动数据块 + 更新所有引用"做碎片整理。原因是:

  • GPU 虚拟地址一旦分配即固定,会被写入内核参数(kernarg)、描述符、以及应用自己保存的指针中;
  • 用户态无法安全地追踪并改写所有这些引用,强行搬迁会导致正在运行的 kernel 访问到错误地址。

因此真正可行的做法分两层:

  • 应用/框架层:通过内存池(见策略2)、按固定尺寸分配、及时释放并复用,从源头减少碎片;必要时由应用主动"释放旧缓冲 → 重新分配 → 自己拷贝数据 → 更新自己的指针",这属于应用可控的显式重排,而非 Runtime 透明搬迁。
  • 内核 amdgpu / TTM 层 :在物理层面,TTM 可以驱逐、迁移 BO 的物理位置,但保持 GPU 虚拟地址不变,因此对应用透明------这也不是用户态可见的"碎片整理"。

结论:不要期望 ROCr 在后台帮你压缩 VRAM 并重定位指针;缓解碎片主要靠分配策略内存池,而不是运行时透明的 compaction。


14.9 性能优化技巧

14.9.1 减少分配次数

cpp 复制代码
// ❌ 差:频繁小分配
for (int i = 0; i < 1000; i++) {
  void* ptr;
  hsa_memory_allocate(region, 4096, &ptr);
  // 使用...
  hsa_memory_free(ptr);
}

// ✅ 好:批量分配
void* large_buffer;
hsa_memory_allocate(region, 1000 * 4096, &large_buffer);
for (int i = 0; i < 1000; i++) {
  void* ptr = (char*)large_buffer + i * 4096;
  // 使用...
}
hsa_memory_free(large_buffer);

14.9.2 选择合适的内存类型

cpp 复制代码
// 根据访问模式选择内存类型

// 场景1: GPU密集计算,CPU很少访问
// → 使用VRAM (Coarse-grained)
hsa_memory_allocate(gpu_local_region, size, &ptr);

// 场景2: CPU-GPU频繁交互
// → 使用System Memory (Fine-grained)
hsa_memory_allocate(system_fine_region, size, &ptr);

// 场景3: 大数据集,低访问频率
// → 使用System Memory (Coarse-grained) + Explicit Copy
hsa_memory_allocate(system_coarse_region, size, &ptr);

14.9.3 利用内存池API

cpp 复制代码
// AMD扩展:使用内存池
hsa_amd_memory_pool_t gpu_pool;
hsa_amd_agent_iterate_memory_pools(
    gpu_agent,
    [](hsa_amd_memory_pool_t pool, void* data) {
      // 查找VRAM池
      hsa_amd_segment_t segment;
      hsa_amd_memory_pool_get_info(
          pool, HSA_AMD_MEMORY_POOL_INFO_SEGMENT, &segment);
      if (segment == HSA_AMD_SEGMENT_GLOBAL) {
        *(hsa_amd_memory_pool_t*)data = pool;
        return HSA_STATUS_INFO_BREAK;
      }
      return HSA_STATUS_SUCCESS;
    },
    &gpu_pool);

// 从池中分配
void* ptr;
hsa_amd_memory_pool_allocate(gpu_pool, size, 0, &ptr);

14.10 调试内存问题

14.10.1 常见内存错误

错误类型 症状 排查方法
内存泄漏 可用内存持续下降 使用valgrind、rocprofiler追踪分配
重复释放 Segment fault 启用HSA_DEBUG,检查释放路径
越界访问 数据损坏、崩溃 使用AddressSanitizer
对齐错误 性能下降、崩溃 检查指针对齐,使用aligned_alloc
类型错误 访问错误的内存 确认region类型和访问agent匹配

14.10.2 环境变量诊断

bash 复制代码
# 启用内存分配追踪
export HSA_ENABLE_DEBUG=1
export HSA_MEMORY_DEBUG=1

# 输出详细日志
export HSA_DEBUG_LEVEL=7

# 每次分配都记录
export HSA_TOOLS_LIB=libhsa-runtime-tools64.so

# 运行程序
./my_app

14.10.3 Driver层调试

cpp 复制代码
// 在Driver层添加日志
hsa_status_t KfdDriver::AllocMemory(...) {
  
  #ifdef HSA_DEBUG
  fprintf(stderr, "[Driver] AllocMemory: node=%u, size=%zu, flags=%x\n",
          node_id, size, flags.Value);
  #endif
  
  HSAKMT_STATUS status = hsaKmtAllocMemory(...);
  
  #ifdef HSA_DEBUG
  fprintf(stderr, "[Driver] AllocMemory: result=%d, addr=%p\n",
          status, *mem);
  #endif
  
  return MapKmtError(status);
}

🎯 本章总结

核心要点回顾

  1. 分配流程分层

    • API层 → MemoryRegion → Agent → Driver → Thunk → KFD
    • 每层有明确职责,Driver是硬件操作的唯一通道
  2. Driver的关键作用

    • 参数转换(HSA ↔ KFD)
    • 接口封装(C++ ↔ C)
    • 错误处理和状态管理
    • GPU地址空间映射
  3. 内存类型差异

    • System Memory:主机内存,需要映射到GPU
    • VRAM:GPU显存,高带宽但容量有限
  4. 对齐与粒度

    • 不同内存类型有不同对齐要求
    • 大页支持可提升大内存性能
  5. VRAM管理

    • 容量有限,需要精心管理
    • 碎片问题需要预防和缓解
  6. 性能优化

    • 批量分配减少开销
    • 选择合适的内存类型
    • 使用内存池API

🔗 导航


相关推荐
DeeplyMind2 个月前
ROCm rocr-libhsakmt分析系列5-1: Event 机制全景—从 Doorbell 到 Signal
libhsakmt·rocr-runtime·hsa event
DeeplyMind3 个月前
Rocm rocr-libhsakmt Event 机制技术文章预告
libhsakmt·rocr-runtime·hsa event
DeeplyMind4 个月前
第05章:HSA-API快速入门
agent·signal·queue·rocm·rocr-runtime·hsa