Intel Discrete GPU - Memory - HMM / Device Pages
HMM 即 Heterogeneous Memory Management,其主要为并行计算加速场景提供系统侧支持,以 GPGPU 通过并行计算场景为例的话,因为 GPU 设备中具备独立显存,在 GPU 执行计算的过程中将其所需数据存储至独立显存的话,GPU 将会发挥更加出色的性能;
并行计算的场景中涉及到 CPU 与 GPU 的频繁交互,为了能够让 GPU 能够发挥了优越的性能,在 CPU 准备好待计算的数据后,就需要在 GPU 启动计算之前将这些数据所占用的内存迁移至 GPU 侧,等待计算完成之后,在 CPU 访问计算结果时,这些结果数据还需要再次迁移至系统内存供 CPU 访问;
这期间在 CPU 与 GPU 侧的内存迁移动作,即为本次所介绍的 HMM 与 Migrate Device 模块共同来实现;
并行计算框架常见的有几种,分别有 CUDA / SYCL / ROCm,SYCL 由 Khronos 组织主推且有 Intel 作为大力的支持者,其编程风格非常符合 C++ 风格,作为一个 C/C++ 开发者,个人是极力支持的,看好它日后的发展;
插播一下,SYCL Tech 提供了一个免费的编写、运行环境,是一个练习的好平台;
即使并行计算框架有多种,但对于系统底层的能力来说,只有 HMM 提供支撑即可;现在我们以 SYCL 为例,看下它是如何借助底层的能力完成并行计算的;
SYCL 中操作内存所使用的方式主要有 Buffer / USM (Unified Shared Memory) 两种,关于这两种方式的介绍,可以通过 codeplay github 中所提供的 PDF 了解下,
USM 的使用场景中,可能涉及到同一份物理内存分别映射到 CPU / GPU 侧,也可能是与 Buffer 场景类似,在 CPU / GPU 侧分别有一份数据存储,

而我们将针对左侧这种场景较深入地解读下底层系统的能力支撑;
在 Linux 内存管理中,每个 Node 内存会按 Zone 来划分,常见的 Zone 有 ZONE_DMA / ZONE_NORMAL / ZONE_MOVABLE 等,除这些以外,还有一类在移动设备中不常见的 ZONE_DEVICE 类型,而 HMM 所涉及的技术领域中,ZONE_DEVICE 就是至关重要的一点;
当 GPU 初始化并行计算能力时,会通过devm_memremap_pages将独立显存中规划的一段空间映射至 CPU 侧,按照系统中的默认页面粒度将这段独立显存空间通过 pages 进行 pfn 映射,这个映射也是一种内存 hotplug,所添加的 pages 会接入到 VMEMMAP 中,并划分至 ZONE_DEVICE 中,通过/proc/zoneinfo能够得知所划分的页面数;

需要注意的是,这些 ZONE_DEVICE 中的 pages 不同普通内存页面,因为它们并不在 Buddy 的管理范围内,内存分配不涵盖这些页面;
当内存在系统内存与独立显存间迁移时,一般通过 Migrate Device 所提供迁移框架中的几步来完成,它们是 migrate_vma_setup / migrate_vma_pages / migrate_vma_finalize,下面分别来看下每个接口的对应能力,
-
migrate_vma_setup()
主要包含
migrate_vma_collect和migrate_vma_unmap两步操作,其中
migrate_vma_collect负责收集 vma migrate 范围所涉及的所有源 pfn,并将拿到的这些 pfn 存储至migrate->src,在这过程中涉及一个优化,将这个 page 对应到本进程的页表 entry 做成 swp 形式;随后
migrate_vma_unmap先通过folio_isolate_lru将源 page 从 LRU 中取下,再通过try_to_migrate将页表对应 entry 做成 migrate entry,前提是这个 page 仍有该进程之外的 map,通过folio_mapped判断,所以上面提到的优化就是针对这里做的,因为try_to_migrate一旦介入将涉及到反向映射的查找,较消耗性能;而 migrate 失败的 entry ,先是在 src_pfns 中清除掉
MIGRATE_PFN_MIGRATE标识,然后将其恢复成原始的 pte,通过remove_migration_ptes(folio, folio, 0)完成 ,需要注意 remove ptes 的 src/dst 参数均为 src folio,所以这个过程中只涉及 migrate entry,还不是 device entry; -
migrate_vma_pages()
移动一些 page 相关的 meta data,如果原始 vma 相关的页表尚未建立映射,那么将通过
migrate_vma_insert_page建立起与 device page 的对应关系; -
migrate_vma_finalize()
这时作为 migrate 的收尾工作,将 migrate entry 移除并通过
remove_migration_ptes(src, dst, 0)完成页表对 dst entry 映射,这时的 dst entry 即为 device entry;
结合这些 Migrate 能力接口,再加上页面内容的传输能力,即可完成系统内存与独立显存之间的数据迁移;
以 SYCL 的内存形式 Buffer / USM 为例的话,这个数据迁移操作会涉及到以下几种场景,
-
Buffer 使用时的主动迁移,从系统内存迁至独立显存
当 SYCL Buffer access 调用时,数据内存将会被触发,AMD 独立显卡内核驱动中提供了一个这样的完整能力接口
svm_migrate_vma_to_vram,可综合以上所述能力接口,是一个好的参考案例; -
USM 使用时的被动迁移,从系统内存迁至独立显存
所谓被动触发,实际意味着数据的迁移动作是由 GPU 访存时缺页触发,参考 Intel Xe 独立显卡内核驱动中的
handle_pagefault实现,缺页流程继而调用到xe_bo_migrate时数据内存将被真正迁移; -
当 CPU 访问已迁移数据时触发被动迁移,从独立显存迁至系统内存
上面 migrate vma 时有提到当内存迁至独立显存后,进程中对应的内存页面所对应的页表项会被替换为 swap entry,这个 swap 为特殊的 device swap entry,当前 CPU 访问到这些 entry 后会触发 fault 并涉及到以下流程,
vm_fault_t do_swap_page(struct vm_fault *vmf) { ... if (is_device_exclusive_entry(entry)) { vmf->page = pfn_swap_entry_to_page(entry); ret = remove_device_exclusive_entry(vmf); } else if (is_device_private_entry(entry)) { ... vmf->page = pfn_swap_entry_to_page(entry); ... ret = vmf->page->pgmap->ops->migrate_to_ram(vmf); put_page(vmf->page); } ... }如果是普通的 device entry,则会调用到指定 device 实现的
migrate_to_ram回调,与上述数据内存迁至独立显存流程类似,将数据内存迁回到系统内存中;如果是 device exclusive entry,说明当前这种使用场景很有可能是 CPU / GPU 均映射到同一块物理内存的场景,而且 GPU 此时是想独占访问权,并通过
make_readable_device_exclusive_entry/make_writable_device_exclusive_entry标记过,如果此时 CPU 想要访问内存,将会通过上面 swap 流程中的remove_device_exclusive_entry通知到 GPU 侧此时独占环境已无;根据官方文档介绍,独立环境一般用于 GPU 访问系统内存且是 atomic 操作环境下;
Some devices have features such as atomic PTE bits that can be used to implement atomic access to system memory.
TODO
-
在
migrate_vma_finalize完成后,原页面是如何处理的,释放? -
USM 场景下 GPU 缺页并 migrate bo 时,并没有对 CPU 侧页表做 device entry,
文档引用
- Linux doc - Heterogeneous Memory Management (HMM)