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 内核发起一次全局内存访问时,地址翻译流程如下:
- GPU 核心发出虚拟地址 VA
- TLB 命中则直接获取物理地址;未命中触发 PTW
- PTW 从当前上下文的页表基址寄存器(对应 CPU 的 CR3)开始逐级遍历
- 若 PTE 有效且权限通过,填充 TLB 并返回物理地址
- 若 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 提供三个核心机制:
- Fault Mirror(缺页镜像):当 CPU 在设备注册的地址范围内触发缺页时,HMM 自动查询设备 MMU 的状态,实现一致的缺页语义。
- Page Migration(页面迁移):提供
migrate_vma()将页面在 CPU RAM 与设备内存之间迁移。 - 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
六、前沿演进与展望
6.1 NVIDIA Grace Hopper NVLink-C2C 对 UM 的重塑
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 框架为异构内存管理提供了通用抽象,但其性能取决于以下工程细节:
- 页迁移延迟:PCIe 带宽限制 + 缺页 Warp 排水开销,单次迁移在 5–20μs 级别
- 预取命中率:基于推理 Scheduler 的 LRU / Clock 策略预测页面访问模式
- TLB 压力:USM / Managed Pointer 在 GPU TLB 中占用条目,需结合巨页优化
- 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+)

发表评论 取消回复