mmap 复兴:当'内存不够'不再是唯一答案
TL;DR
本文核心观点:
- 核心概念 — GraphZero 用 C++ 和 mmap 绕过 PyTorch Geometric 的内存限制,按需加载超出物理内存的图数据
- 关键机制 — mmap 建立文件到虚拟地址空间的映射,page fault 触发 OS 换页,开发者像访问普通指针一样遍历图结构
- 实际效果 — 操作系统自动管理哪些节点/边在内存、哪些在磁盘,避免 OOM 的同时处理任意规模图数据
- 延伸洞察 — 被忽视几十年的 Unix 技术正在 AI 时代复兴,”scale smart”优于”scale up”
OOM 噩梦
如果你用 PyTorch Geometric (PyG) 训练过图神经网络,一定见过这个画面:模型训练到一半,CUDA OOM 错误突然弹出,几个小时的计算全部白费。或者更糟的:OOM 发生得越来越频繁,从每天一次到每次运行必定崩溃——你已经不知道是真的内存不够,还是 PyG 的 DataLoader 在某个隐藏角落悄悄把整张图塞进了显存。
这是 Graph Neural Network (GNN) 开发者的日常噩梦。GNN 不同于普通神经网络——它需要存储整张图的邻接关系,内存消耗随节点数呈平方级增长。
行业的默认解法是什么?
- 加内存 — 买更大的 GPU(A100 40GB → H100 80GB)
- 买更多机器 — 分布式训练,把图切分到多个节点
- 妥协模型 — 减小 batch size,降低模型复杂度
这些方案都有效,但都有一个共同点:用资源换解决问题。
被遗忘的 Unix 智慧
Unix 的古老智慧
mmap(memory-mapped files)是 Unix 系统调用,最早出现在 1970 年代的 Research Unix。它的核心思想很简单:
让文件看起来像内存,让内存看起来像文件。
💡 Key Insight
mmap 最早出自 1970 年代的 Research Unix,在 SSD 出现之前因 page fault 代价太高而被遗忘;今天它的复兴恰恰说明:上古智慧与现代硬件的组合,往往比追逐新框架更可靠。
传统 I/O 模式需要先把整个文件从磁盘读入内存,才能开始处理——对于 GB 级的图数据,这个预加载过程本身就会触发 OOM。mmap 则是另一种思路:只建立映射,不预加载,按需触发 page fault 由操作系统将磁盘页写入内存。开发者像访问普通指针一样遍历图结构,操作系统在后台做换页决策。
mmap 不一次性把整个文件读入内存,而是按需将磁盘页加载到内存。操作系统负责换页,开发者像访问普通内存一样访问文件。
为什么被遗忘了?
- SSD 之前,磁盘太慢
- HDD 随机读取 1-10 ms
- mmap 的页错误(page fault)代价太高
- 一次性读入内存反而更快
- 框架的”易用性优先”哲学
- PyTorch、TensorFlow 默认把所有数据加载到内存/显存
- mmap 需要手动管理,API 不够友好
- “开发者体验”战胜了”资源效率”
- 硬件摩尔定律的诅咒
- 内存便宜了 → 懒得优化
- GPU 显存大了 → 更懒得优化
- 问题被硬件进步掩盖了
SSD 改变了一切
现代 NVMe SSD 的随机读取延迟已经降到 10-100 微秒——比 HDD 快 100-1000 倍。
这意味着:
- mmap 的页错误代价大幅降低
- SSD 带宽足以支撑许多 AI 工作负载
- “内存不够加内存”不再是唯一答案
💡 Key Insight: SSD 的普及重新定义了 mmap 的可行性边界。当随机读取延迟从毫秒级降至微秒级,”按需加载”的方案从理论不可能变成工程上可行。
GraphZero:绕过 PyG 的内存墙
问题背景
GraphZero 的开发者遇到了一个典型问题:图数据太大,无法装入内存,PyG 的 DataLoader 试图一次性加载整张图,即使买了 64GB 内存的机器,OOM 依然发生。
解决方案
他们没有选择:
- ❌ 买更大内存的机器
- ❌ 改用小 batch 的采样策略(损失精度)
- ❌ 用分布式训练(复杂度爆炸)
而是选择了:
- ✅ 用 C++ 和 mmap 直接从 SSD 加载数据
- ✅ 绕过 PyG 的内存分配,自己管理数据流
- ✅ 让操作系统处理 paging,开发者专注于算法
💡 Key Insight
GraphZero 用 C++ 和 mmap 直接从 SSD 加载数据,绕过 PyG 的内存分配,自己管理数据流。选择这条路的代价是复杂度上升,但换来了处理任意规模图数据的可能性。
核心实现思路
GraphZero 的实现核心是:将图数据以二进制格式存储在 SSD 上,通过 mmap 将文件映射到虚拟地址空间。访问图数据时,系统触发 page fault,操作系统将对应磁盘页加载到内存。这种”懒加载”机制使得处理超出物理内存的图数据成为可能——操作系统自动管理哪些节点/边在内存、哪些在磁盘,开发者只需像访问普通指针一样遍历图结构,无需关心换页逻辑。
性能权衡
| 指标 | 全内存方案 | mmap 方案 |
|---|---|---|
| 首次访问延迟 | 低(已加载) | 高(page fault) |
| 内存占用 | 高(全图常驻) | 低(按需加载) |
| 吞吐量 | 极高 | 中等(受 SSD 限制) |
| 可处理图规模 | 受限于内存 | 受限于磁盘 |
| 复杂度 | 低(框架默认) | 高(需自定义) |
GraphZero 的选择逻辑:
“慢但可用 > 快但崩溃”
mmap 的四个 AI 场景
场景一:大模型推理的 KV Cache
现代 LLM 的 KV Cache 可以占用数十 GB 显存。某些场景下:
- 长上下文需要存储大量历史状态
- 但访问模式是顺序/局部的
- mmap 可以把不活跃的 KV Cache 换出到 SSD
场景二:大规模数据集训练
ImageNet、LAION-5B 级别的数据集:
- 无法全部装入内存
- 传统方案:复杂的数据加载 pipeline
- mmap 方案:直接映射文件,让 OS 优化缓存
场景三:图神经网络
GNN 的特殊性:
- 邻接矩阵稀疏但巨大
- 消息传递是局部的(只访问邻居)
- 完美的 mmap 场景:按需加载局部子图
💡 Key Insight
GNN 的邻接矩阵天然适合 mmap 的局部访问模式:消息传递每次只访问当前节点的邻居,不需要整张图常驻内存。按需加载局部子图,是 mmap 在 GNN 场景的核心优势。
场景四:Embedding 查找表
推荐系统的 embedding 表:
- 几亿用户 × 几百维 = TB 级
- 访问模式极度稀疏(一次只查几个)
- mmap 让 TB 级 lookup 在 GB 级内存机器上可行
Unix 哲学的现代回响
GraphZero 的做法让我想起 Unix 哲学的核心原则:
做一件事,并做好
mmap 只做一件事:把文件映射到内存。不试图解决所有问题。
对比现代框架的”一站式”哲学,mmap 是克制的艺术。
利用操作系统的力量
GraphZero 没有自己实现 paging 算法,而是:
- 信任 Linux 的页面置换策略
- 利用 OS 的文件系统缓存
- 站在巨人的肩膀上
延迟加载
不预分配,不预加载,按需获取。
这与现代软件开发的”提前优化”倾向形成对比。
资源约束催生创新
历史案例:
- Doom (1993):用二进制空间分割 (BSP) 在 4MB 内存运行 3D 游戏
- SQLite:把整个数据库映射到内存,无需服务器
- Redis:内存数据库,但用 fork + COW 做持久化
GraphZero 继承了这一传统:限制不是障碍,是创新的催化剂。
💡 Key Insight
限制不是障碍,是创新的催化剂。
mmap 的适用边界
适合 mmap 的场景
| 场景 | 原因 |
|---|---|
| 数据 > 内存,但访问局部 | 只需要一小部分数据在内存 |
| 访问模式可预测 | OS 的预读机制能有效工作 |
| 对延迟不敏感 | 可以接受偶尔的 page fault |
| 只读或写少 | 避免复杂的 write-back 逻辑 |
| SSD 存储 | 随机读取性能足够好 |
不适合 mmap 的场景
| 场景 | 原因 |
|---|---|
| 随机访问整个数据集 | page fault 风暴,性能崩溃 |
| 对延迟极度敏感 | 实时系统无法容忍不可预测的延迟 |
| 频繁写入 | write-back 复杂性,数据一致性风险 |
| HDD 存储 | 机械硬盘的随机读取太慢 |
| 网络文件系统 | NFS 的 mmap 支持有问题 |
GraphZero 为什么适合?
- GNN 训练是批量顺序访问(遍历邻接节点)
- 图数据只读(训练时不动图结构)
- 用SSD存储数据集
- 可以容忍毫秒级的加载延迟(相对于训练时间微不足道)
结尾
GraphZero 让我想起了计算机科学的一条基本真理:
“计算机科学中的所有问题都可以通过增加一个抽象层来解决,除了抽象层太多的问题。”
在 AI 基础设施疯狂堆砌抽象的今天(Kubernetes → Docker → CUDA → PyTorch → PyG → …),GraphZero 选择了一条相反的路:
回到 Unix 的基础,用最原始的系统调用解决问题。
💡 Key Insight
在 AI 基础设施疯狂堆砌抽象的今天,GraphZero 选择了一条相反的路:回到 Unix 的基础,用最原始的系统调用解决问题。
这不是倒退,是进化。
当行业沉迷于”scale up”(买更大的机器)时,GraphZero 证明了”scale smart”(更聪明地使用资源)的价值。
mmap 不是银弹。但在正确的场景下,它是一把被忽视的利器。
最好的优化往往不是买更多硬件,而是重新思考问题边界。
深度阅读时间:约 9 分钟
参考来源
- GraphZero GitHub Repository
- mmap(2) - Linux Manual
- The Evolution of Storage: From HDD to NVMe
- Memory Management in PyTorch
- “What Every Programmer Should Know About Memory” - Ulrich Drepper
本文灵感源自 2026-03-16 Reddit Daily Digest 中关于 GraphZero 的讨论。
💬 评论
💡 使用 GitHub 账号登录 即可参与讨论