Hotdry.

Article

将 NVIDIA GPU 显存映射为 Linux Swap:CUDA Unified Memory 与 FUSE 方案实战

探讨通过 FUSE 文件系统与 CUDA Unified Memory 将 NVIDIA GPU 显存映射为 Linux swap 的技术路径、配置参数及死锁风险规避策略。

2026-06-02systems

在运行大型语言模型或处理高分辨率图形工作负载时,系统内存不足往往成为瓶颈。对于配备大容量显存(VRAM)但系统内存有限的机器,一个直观的想法是:能否将闲置的 GPU 显存借用为系统 swap 空间?本文分析两种主流技术路径 —— 基于 MTD 子系统的传统方案和基于 FUSE 的 vramfs 方案 —— 并探讨其背后的 CUDA Unified Memory 机制与实际操作中的风险边界。

背景:为什么需要 VRAM Swap

现代 GPU 常配备 8GB、12GB 甚至 24GB 以上的显存,而部分工作站或边缘设备的系统内存可能仅有 16GB 或更少。当运行内存密集型应用(如 LLM 推理、视频编辑或科学计算)时,系统内存耗尽会触发 OOM Killer,而显存可能仍有大量剩余。传统的磁盘 swap 虽然可以缓解内存压力,但其随机 I/O 性能远低于显存的 GDDR6/GDDR6X 带宽。

NVIDIA 在 CUDA 6 中引入了 Unified Memory(UVM)特性,通过 cudaMallocManaged() 提供单一指针让 CPU 和 GPU 共享内存池,运行时自动在主机和设备间迁移页。然而,UVM 的显存 oversubscription 行为在 Linux 上与 Windows 存在显著差异 —— 当 GPU 显存耗尽时,Linux 驱动长期以来缺乏有效的共享内存回退机制,这也是社区反复呼吁解决的问题。

技术路径一:MTD 子系统(传统方案)

Linux 的 MTD(Memory Technology Device)子系统允许将特定内存区域映射为块设备。对于较老的显卡或开源驱动(nouveau),可以通过 phramslram 模块将 PCI 地址空间中的显存区域暴露为 /dev/mtdblock 设备。

实现步骤包括:首先通过 lspci -vvv 获取 VGA 控制器的内存区域地址,识别出 prefetchable、64-bit 的最大内存区域;然后计算可用范围(通常需要保留部分显存供显卡自身使用,如保留 32MB-64MB 作为 framebuffer);接着配置 phram 模块参数,将指定 PCI 地址范围映射为 MTD 设备;最后使用 mkswapswapon 激活 swap。

然而,该方案存在根本性限制:NVIDIA 专有驱动会直接管理显存,与 MTD 子系统冲突,导致 Xorg 或 Wayland 会话崩溃。因此,这一方法仅适用于使用开源 nouveau 驱动的场景,对于需要 CUDA 支持的生产环境并不实用。

技术路径二:FUSE 文件系统(vramfs 方案)

vramfs 是一个基于 FUSE 的用户态文件系统,通过 OpenCL 在 GPU 显存中分配缓冲区,并将其暴露为普通文件。与 MTD 方案不同,vramfs 通过 GPU 驱动提供的 API 分配显存,因此与 NVIDIA 专有驱动兼容。

部署流程相对简洁:安装 vramfs 后,创建挂载点并分配显存空间(如 vramfs /tmp/vram 256MB -f),然后在 vramfs 中创建 swapfile(mkswap -U clear --size 200M --file /tmp/vram/swapfile),最后使用 swapon 激活。这种方式的优势在于无需修改内核模块参数,且可以动态调整大小。

但 vramfs 存在一个致命风险:死锁。当系统内存压力极高时,vramfs 进程本身可能被内核 swap 到它自己管理的 VRAM swap 空间中,导致无法换回,系统完全冻结。社区提供的解决方案是通过 systemd 服务启动 vramfs,并设置 MemorySwapMax=0 禁止该进程被 swap 出去,从而打破循环依赖。

CUDA Unified Memory 的期望与现实

理论上,CUDA Unified Memory 应该自动处理显存 oversubscription,当 GPU 显存不足时将数据迁移到系统内存,甚至到磁盘 swap。NVIDIA 官方文档描述 UVM 可以 "自动在主机和设备间迁移数据页,使数据对 CPU 代码表现为 CPU 内存,对 GPU 代码表现为 GPU 内存"。

然而,GitHub 上的 issue #663 揭示了 Linux 用户的长期困扰:nvidia_uvm 模块虽然在 lsmod 中显示已加载,但当 VRAM 满时并不会像 Windows 那样自动使用共享系统内存作为后备,而是直接报错或崩溃。这与 AMD 和 Intel 在 Linux 上的驱动行为形成对比 —— 后两者的共享内存机制工作正常。

对于开发者而言,这意味着不能单纯依赖 UVM 的自动迁移来解决显存不足问题,而需要显式管理内存分配,或借助 vramfs 等方案将显存反向提供给系统使用。

可落地的配置参数

如果决定部署 vramfs 方案,以下参数和监控点值得注意:

显存预留计算:为 GPU 保留足够的显存供图形子系统使用。对于桌面环境,建议至少保留 256MB-512MB;对于纯计算节点(无显示输出),可以保留更少(如 64MB-128MB)。计算公式:可用显存 = 总显存 - 预留显存 - 其他应用占用

Swappiness 调优:当 VRAM swap 的随机 I/O 性能显著高于磁盘时,提高 swappiness 可能有利。例如,若 VRAM swap I/O 速度是磁盘的 2 倍,可将 swappiness 设为 133(默认 60)。计算公式:swappiness = 100 × (VRAM_swap_speed / disk_swap_speed)

死锁规避:必须通过 cgroup 或 systemd 的 MemorySwapMax=0 确保 vramfs 进程不会被 swap 出去。同时,建议设置 TimeoutStartSec=0 避免启动超时。

监控指标:定期检查 swapon -s 确认 swap 状态;监控 /proc/mtd 确认 MTD 设备(如使用传统方案);使用 nvidia-smi 跟踪显存使用情况,确保不会过度分配导致 GPU 应用崩溃。

Swapfile 连续性:vramfs 创建的 swapfile 可能存在空洞(holes),导致 swapon 报错。解决方法是使用 loop 设备:通过 truncate 创建固定大小文件,losetup 绑定到 loop 设备,然后对 loop 设备执行 mkswapswapon

风险权衡与替代方案

vramfs 方案虽然可行,但生产环境部署需谨慎评估。除死锁风险外,还需考虑:显存断电即失的特性意味着系统崩溃时 swap 中的数据无法恢复;频繁的 CPU-GPU 数据传输会消耗 PCIe 带宽,影响其他 GPU 计算任务;NVIDIA 驱动的更新可能改变显存管理行为,导致兼容性问题。

对于 AI 工作负载,更稳健的方案可能是:使用支持 offload 的推理框架(如 llama.cpp 的 GPU offload 参数),显式控制哪些层驻留显存;采用模型量化(INT8/INT4)减少显存占用;或使用支持 unified memory 的 CUDA 应用并监控其迁移行为。

总结

将 GPU 显存映射为 Linux swap 是一个技术上可行但充满权衡的方案。MTD 子系统方案受限于与 NVIDIA 专有驱动的兼容性,而 vramfs 方案虽然兼容性好,但需要仔细配置以避免死锁。CUDA Unified Memory 在理论上提供了更优雅的内存 oversubscription 机制,但 Linux 驱动实现上的限制使其无法成为通用的解决方案。

对于资源受限的环境,vramfs 可以作为应急手段,但应配合 cgroup 保护、合理的 swappiness 调优和完善的监控。长期来看,关注 NVIDIA 开源内核模块的进展,以及社区对 UVM 共享内存支持的持续呼吁,可能是更可持续的路径。


资料来源

  • ArchWiki "Swap on video RAM":MTD 子系统与 vramfs 配置细节
  • NVIDIA Developer Blog "Unified Memory in CUDA 6":UVM 基础架构说明
  • GitHub NVIDIA/open-gpu-kernel-modules Issue #663:Linux UVM 共享内存问题讨论

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com