GPU 缺页中断与 CUDA Unified Memory — 从 HMM 框架到 AI 推理大显存管理的工程实践

一、为什么 GPU 内存管理是 AI 推理的核心瓶颈

2026 年的 AI 推理基础设施面临一个愈发尖锐的矛盾:大语言模型的权重与 KV Cache 需求仍在以指数级增长,而单一 GPU 的 HBM 容量仍停留在 80GB(H100)至 192GB(GB200)量级。当一个 700 亿参数的 KV Cache 在 FP16 下就需要超过 1.4TB 显存时,单卡已远远无法容纳完整的推理工作集。

传统解决方案无非三种:多卡张量并行、CPU-GPU 分层卸载、以及显存虚拟化。前两者各有代价——张量并行引入 NVLink 通信开销并提升 TCO,CPU-GPU 卸载受限于 PCIe 延迟(一次 GPU 缺页触发 CPU 内存重填需要 5–10μs 量级,比 HBM 访问慢 200 倍以上)。

CUDA Unified Memory(简称 UM)提供了一条工程化路径:通过软硬件协同的按需页迁移,让 GPU 可以透明地访问主机内存,使得"可用显存"远大于物理 HBM。本文从 GPU 缺页中断机制入手,深入剖析 Linux 内核 HMM 框架的实现原理,并给出在 AI 推理生产环境中利用 UM 管理超大规模显存的实战方法论。

二、GPU MMU 架构:从物理寻址到虚拟寻址的演进

2.1 GPU 页表层级结构

现代 GPU(以 NVIDIA Hopper 架构为例)拥有完整的MMU(Memory Management Unit),支持多级页表遍历。与 CPU 类似,GPU 页表的页表项(PTE)中包含物理页帧号(PFN)、访问权限位(R/W/X)、有效位(Valid)、脏位(Dirty)以及 Coherent/Passthrough 等控制位。

GPU 虚拟地址 (49-bit)
┌──────────┬──────────┬──────────┬──────────┬───────────┐
│ PML4E    │ PDPE     │ PDE      │ PTE      │ Offset    │
│ (9-bit)  │ (9-bit)  │ (9-bit)  │ (9-bit)  │ (12-bit)  │
└──────────┴──────────┴──────────┴──────────┴───────────┘

H100 支持 4-level 和 5-level 两种页表格式(对应 48-bit 和 57-bit 虚拟地址),页大小可为 4KB、2MB 或 1GB。GPU 内部有一个 PTW(Page Table Walker)单元专门负责硬件级页表遍历。

当 GPU 内核发起一次全局内存访问时,地址翻译流程如下:

  1. GPU 核心发出虚拟地址 VA
  2. TLB 命中则直接获取物理地址;未命中触发 PTW
  3. PTW 从当前上下文的页表基址寄存器(对应 CPU 的 CR3)开始逐级遍历
  4. 若 PTE 有效且权限通过,填充 TLB 并返回物理地址
  5. 若 PTE 无效或权限不足,触发 Page Fault 中断

2.2 GPU Page Fault 中断与 CPU 缺页的根本差异

CPU 缺页中断是可重入的——CPU 处理缺页后重新执行触发缺页的指令即可。但 GPU 缺页不同:GPU SIMT(单指令多线程)架构下,一个 Warp(32 个 Thread)中的所有线程共享指令指针,无法单独重试某个线程的内存访问。NVIDIA 的解决方案是:

  • Warp 级暂停(Warp Stall):触发缺页的整个 Warp 被暂停
  • Fault Queue(缺页队列):GPU MMU 将缺页地址写入硬件队列,触发中断驱动 Pager 处理
  • 批量重试(Batch Retry):同一 Warp 中多个线程的缺页合并处理后,Warp 恢复执行

这就是为什么 GPU 缺页代价远高于 CPU:不仅缺页处理本身有延迟,整个 Warp 还需要被排水(drain)和重填。

三、CUDA Unified Memory:按需页迁移的协议栈

3.1 UM 的内存模型

// 最简单的 Unified Memory 分配
cudaMallocManaged(&ptr, size);  // 分配 size 字节的托管内存

// 或使用 cudaMalloc 后注册为托管
cudaMalloc(&device_ptr, size);
cudaMemAdvise(device_ptr, size, cudaMemAdviseSetPreferredLocation, cpu);

CUDA UM 在驱动层构建了一张跨设备的统一地址空间:同一虚拟地址在 CPU 和设备端均可访问,驱动负责在缺页时迁移对应物理页。

关键概念:

  • Residency(驻留):页当前所在的物理位置(GPU HBM 或 CPU RAM)
  • Access Counter(访问计数器):硬件或软件维护的页访问频率
  • Prefetch(预取):迁移前主动将页面拉到 GPU,减少同步等待
  • Advise(建议):程序提示运行时预期的访问模式

3.2 页迁移的协议栈

一次完整的 UM 缺页迁移在 Linux 上涉及以下组件:

CUDA UM 缺页处理栈:
┌──────────────────────────────────────────────┐
│  cudaMallocManaged() 用户代码                  │
├──────────────────────────────────────────────┤
│  NVIDIA GPU Driver (nvidia.ko)                │
│  └── GMMU (GPU MMU) 管理 / UVM (Unified      │
│      Virtual Memory) 模块                     │
├──────────────────────────────────────────────┤
│  Linux Kernel HMM (Heterogeneous Memory Mgmt) │
│  └── 页面迁移 / fault mirroring /             │
│      migrate_to_ram / migrate_to_device       │
├──────────────────────────────────────────────┤
│  IOMMU (SMMU on ARM64 / VT-d on x86-64)       │
│  └── GPU 地址空间访问越界中断                 │
└──────────────────────────────────────────────┘

四、Linux 内核 HMM:异构内存管理框架

4.1 HMM 的设计目标与接口

HMM(Heterogeneous Memory Management)由 Jérôme Glisse 在 2014 年提出并合入 Linux 4.14,初衷是让异构设备(GPU、FPGA、RDMA 网卡等)能直接访问 CPU 虚拟地址空间,而不需要传统的 GEM/TTM 内存锁定拷贝模型。

HMM 提供三个核心机制:

  1. Fault Mirror(缺页镜像):当 CPU 在设备注册的地址范围内触发缺页时,HMM 自动查询设备 MMU 的状态,实现一致的缺页语义。
  2. Page Migration(页面迁移):提供 migrate_vma() 将页面在 CPU RAM 与设备内存之间迁移。
  3. Device Exclusive(设备独占):允许设备以独占模式访问某段内存,CPU 端暂时无法访问(用于 RDMA 等场景)。

4.2 HMM 缺页处理流程

/* HMM 缺页处理的核心调用链(简化) */

handle_mm_fault()
  └── hmm_vmirq_fault()    /* HMM: 设备端缺页镜像 */
       └── device_ops->fault_handler()
            └── nvidia_uvm_migrate_fault()
                 /* 
                  * 步骤 1: 在设备 MMU 中查找原 PTE 状态
                  * 步骤 2: 若页面在 CPU 内存中未迁移:
                  *         → 分配 device memory
                  *         → DMA copy CPU → Device (GPU)
                  *         → 更新设备 PTE (Valid=1, PFN=device PA)
                  * 步骤 3: 若页面从未分配过:
                  *         → 分配 device memory
                  *         → 写入初始数据(通常清零或复制)
                  *         → 更新设备 PTE
                  * 步骤 4: 填充 GPU TLB / 刷新 GPU 页表
                  * 步骤 5: 恢复触发缺页的 Warp 执行
                  */

关键路径中的 migrate_vma() 负责实际的页数据搬运:

/* 简化的页面迁移流程(来自 mm/migrate.c) */
int migrate_vma_pages(struct migrate_vma *migrate)
{
    for_each_page_in_range(pfn) {
        /* 源页面:来自 CPU RAM */
        src_page = pfn_to_page(pfn);

        /* 目标页面:GPU HBM */
        dst_page = alloc_device_page();

        /* 页面内容拷贝:CPU 或 GPU 执行 */
        if (use_gdma)
            status = nvidia_dma_copy(dst, src, PAGE_SIZE);
        else
            memcpy(page_address(dst_page), page_address(src_page), PAGE_SIZE);

        /* 更新设备 PTE */
        set_dev_pfn(dst_pfn, dst_page);

        /* 原子替换 CPU 页表项(CPU 端标记为迁移中或已迁出) */
        replace_page_entry(ptep, src_page, dst_page);
    }
}

4.3 HMM 与 NVIDIA UVM 的交互

NVIDIA 的 CUDA Unified Memory Driver(UVM)作为 HMM 的一个 Client 实现,注册 hmm_device_ops:

static const struct hmm_device_ops nv_hmm_ops = {
    .fault    = nv_uvm_fault_migrate,  /* 设备缺页回调 */
    .migrate  = nv_uvm_migrate_page,    /* 页面迁移回调 */
    .snapshot = nv_uvm_snapshot,        /* CPU 缺页镜像查询 */
};

当 CUDA UM 分配的内存页被 CPU 访问时(CPU 缺页触发 handle_mm_fault),HMM 通过 hmm_range_fault() 查询 UVM 是否需要在搬迁页面到 CPU RAM 还是迁移到其他 NUMA 节点。

五、AI 推理生产环境中的 UM 工程实践

5.1 超大模型 KV Cache 管理

在 vLLM 或 TGI 等生产级推理框架中,物理 KV Cache Block 的典型策略是:

  • 核心 Hot Cache:分配到 GPU HBM,保证活跃 Sequence 的低延迟访问
  • Cold Cache / 预填充中间态:通过 CUDA UM 托管,在需要时按需迁移
# 伪代码:基于 UM 的分层 KV Cache 调度
class UnifiedKVCacheManager:
    def __init__(self, budget_ratio=0.7):
        # HBM 中保留 70% 显存给 Hot Cache
        self.hot_blocks = allocate_gpu_hbm(hbm_size * budget_ratio)
        # UM 托管的 Cold Cache
        self.cold_blocks = cuda_malloc_managed(total_seq_len * kv_dim * dtype_size)

    def prefetch_to_gpu(self, block_ids):
        """推测性预取:当预测某 Sequence 即将活跃时触发"""
        for bid in block_ids:
            cuda.mem_prefetch_async(
                cold_ptrs[bid], 
                block_size,
                device_id=0,
                stream=precompute_stream
            )

5.2 推理服务的延迟调优

生产环境中使用 UM 的关键挑战:延迟抖动。缺页导致的单次 5–10μs 延迟在 batch size > 32 时会被放大为明显的 P99 延迟劣化。

策略一:CUDA Async Prefetch + Stream 分离

#include <cuda_runtime.h>

// 预取与计算分离:独立 Stream 处理迁移
cudaStream_t prefetch_stream, compute_stream;
cudaStreamCreate(&prefetch_stream);
cudaStreamCreate(&compute_stream);

// 在 batch N+1 的计算同时,预取 batch N+2 的数据
cudaMemPrefetchAsync(d_data, size, deviceId, prefetch_stream);
kernel<<<grid, block, 0, compute_stream>>>(d_data);

// 预取完成后通过事件同步
cudaEventRecord(prefetch_done, prefetch_stream);
cudaStreamWaitEvent(compute_stream, prefetch_done, 0);

策略二:Huge Page 减少缺页次数

UM 的默认粒度是 4KB,一个 64MB 的 KV Cache Block 会在首次访问时产生 16,384 次缺页。使用 2MB 或 1GB 大页可将缺页次数降至 32 次或 1 次:

// 使用 HostAlloc 注册大页 + cuMemMap 映射 Device Memory
cudaHostAlloc(&h_buf, size, cudaHostAllocNumaUser | cudaHostAllocMapped);
cudaMemRegister(h_buf, size, cudaMemRegisterNumanode);
cudaMemMap(d_ptr, size, offset, handle, flags);

策略三:Access Counter 驱动的页面放置

利用 cudaMemAdviseSetAccessedBy 告诉驱动"该数据会被 GPU 频繁访问",驱动会提前将页面迁移到 GPU 而非等到缺页才被迁移:

cudaMemAdvise(kv_cache_ptr, kv_size, 
              cudaMemAdviseSetAccessedBy, gpu_device_id);
cudaMemPrefetchAsync(kv_cache_ptr, kv_size, gpu_device_id, stream);

5.3 生产环境中的故障排除

典型问题一:Page Storm(缺页风暴)

当 batch size 突增导致大量 GPU 内核并发访问尚未迁移的 UM 页面时,GPU 的 Fault Queue 会溢出,导致驱动强制 Evict 部分页面。诊断方法:

# nvidia-smi 查看 GPU 内存迁移统计
nvidia-smi dmon -s m -d 1  # 监控显存使用/迁移速率

# 使用 Nsight Systems 跟踪缺页事件
nsys profile --trace=cuda,nvtx --gpu-metrics-device=all ./inference_server

典型问题二:NUMA 拓扑不匹配

在双路 EPYC 或双插槽 ARM 服务器上,GPU 通常绑定到 Socket 0 的 NUMA 节点。如果 CUDA UM 在 Socket 1 上分配了 CPU 页面,迁移 PCIe 路径跨越 NUCE(NUMA Coherency),带宽减半:

# 检查 GPU NUMA 亲和性
cat /sys/class/drm/card0/device/numa_node

# 将 UM 分配绑定到 GPU 本地 NUMA 节点
numactl --membind=0 --cpunodebind=0 ./inference_server

典型问题三:GPU 内存碎片化

频繁的 UM 迁移会导致 GPU HBM 出现碎片化,大连续分配失败。解决方案:

  • 使用 CUDA Virtual Memory Management API(cuMemCreate / cuMemMap)显式管理虚拟地址空间
  • 预留 HBM Pool:CUDA_VISIBLE_DEVICES 控制可见设备,避免多推理实例竞争 HBM

六、前沿演进与展望

Grace Hopper 超级芯片通过 NVLink-C2C 将 CPU 内存与 GPU HBM 物理统一(Unified Memory 字面意义),消除 PCIe 带宽和延迟瓶颈。此时 HMM 框架退化为轻量级协调层,UM 缺页延迟降至亚微秒级。这本质上是 HBM 化 HPB(High Bandwidth Memory Pool)的硬件实现。

6.2 CXL 3.0 与 Type 3 设备

CXL 3.0 的 Switch 和 Shared Memory 功能允许 GPU 将主机 CXL-attached DRAM 视为本地 HBM 的扩展层。Linux 内核的 HMM 和 DMABUF Heaps 已经在适配 CXL Type 3 设备,未来 UM 页面可直接驻留在 CXL 内存池,迁移延迟进一步降低。

6.3 Linux 内核动态巨页(Dynamic Hugepage)对 UM 的支持

Linux 6.x 引入的 Dynamic Hugepage 允许内核运行时将多个 4KB 页面合并为 2MB 或 1GB 巨页,对 UM 场景而言这意味着:驱动无需显式请求 Hugepage 也能自动享受巨页带来的 TLB 覆盖率和缺页频率改善。

七、小结

CUDA Unified Memory 并非银弹,它是 NVIDIA 专有技术栈中的硬核优化路径之一。在 Linux 内核层面,HMM 框架为异构内存管理提供了通用抽象,但其性能取决于以下工程细节:

  1. 页迁移延迟:PCIe 带宽限制 + 缺页 Warp 排水开销,单次迁移在 5–20μs 级别
  2. 预取命中率:基于推理 Scheduler 的 LRU / Clock 策略预测页面访问模式
  3. TLB 压力:USM / Managed Pointer 在 GPU TLB 中占用条目,需结合巨页优化
  4. NUMA 亲和性:CPU 端页面应尽量分配在 GPU 本地 NUMA 节点

在 AI 推理生产系统中,UM 最适合作为分层存储的补充层(而非替代层):HBM 驻留 Active Working Set,UM Prefetch 预热即将访问的 KV Cache Block,PCIe 空闲带宽利用页面迁移隐性完成。当这三层有机结合时,才能在 TCO 与推理延迟之间找到最优平衡点。


关键参考: - NVIDIA Unified Memory Programming Guide, CUDA 12.x - Linux Kernel Documentation: Documentation/mm/hmm.rst - Glisse, J. "Heterogeneous Memory Management", Linux Plumbers Conference, 2017 - CUDA UVM Driver: nvidia-uvm.ko source (on kernel 5.x+)

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ .skip-link { position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } .skip-link:focus { top: 0; outline: 3px solid #0056b3; }