Apple Silicon 上 LLM 推理的性能优化
Hypura 开源项目用存储层感知调度优化 Apple Silicon 推理性能。对在 Mac 上部署 LLM 的开发者有实用价值。
Hypura 开源项目用存储层感知调度优化 Apple Silicon 推理性能。对在 Mac 上部署 LLM 的开发者有实用价值。
Hypura 是一个为 Apple Silicon 设计的存储层级感知 LLM 推理调度器。它根据访问模式、带宽成本和硬件能力,在 GPU、RAM 和 NVMe 存储层之间放置模型张量 —— 使超过物理内存的模型能够运行,而不会导致系统崩溃。
在 32 GB Mac Mini 上以 2.2 tok/s 的速度运行 31 GB 的 Mixtral 8x7B。在同一台机器上以 0.3 tok/s 的速度运行 40 GB 的 Llama 70B。而原生的 llama.cpp 在两种情况下都会崩溃。
消费级硬件(MacBook Pro、Mac Studio)配备快速统一内存和 NVMe 存储,但容量有限。一台 32 GB 的 M1 Max 无法简单地加载 40 GB 的模型 —— 操作系统会进行交换颠簸,直到 OOM 清理程序介入。
Hypura 通过理解模型架构来解决这个问题:
规范和嵌入层 虽然很小,但每个 token 都会访问 —— 固定在 GPU 上
MoE 专家路由 利用稀疏性 —— 每个 token 仅有 8 个专家中的 2 个激活。路由拦截在 eval 回调中识别选定的专家,然后仅从 NVMe 加载需要的专家步幅(I/O 减少 75%)。神经元缓存跟踪跨 token 加载的专家切片,通过时间局部性实现 99.5% 的命中率。共同激活跟踪预测哪些专家将在下一步激活,以进行推测性预取。
密集 FFN 权重(gate、up、down —— 约占模型大小的 60%)从 NVMe 通过动态大小的池缓冲流出,而注意力和规范保持 GPU 驻留。预取前瞻深度随可用内存自动扩展。
结果是:那些在朴素 mmap 下会导致机器崩溃的模型变成了可运行的。那些能放入内存的模型以完整的 Metal GPU 速度运行,零开销。
Hypura 读取 GGUF 文件,对硬件进行分析(GPU 工作集、RAM、NVMe 带宽),并求解一个放置优化问题,将每个张量分配到一个存储层:
GPU(Metal) —— 注意力层、规范、嵌入。访问速度最快,受 recommendedMaxWorkingSetSize 限制。
RAM —— 不适合在 GPU 工作集中的溢出层。通过 mmap 访问。
NVMe —— 其余层通过直接 I/O(F_NOCACHE + pread)按需加载,在前向传递之前预取。
Hypura 根据模型大小、架构和可用内存自动选择最佳推理模式:
Full-resident —— 模型完全适合 GPU+RAM。无 NVMe I/O。完整 Metal 速度。
Expert-streaming —— 对于 MoE 模型(Mixtral)。仅非专家张量(~1 GB)保留在 GPU 上。专家张量根据需要从 NVMe 通过池缓冲流出,带有神经元缓存(99.5% 命中率),可消除预热后的大部分 I/O。
Dense FFN-streaming —— 对于过大而无法放入 GPU 的密集模型(Llama 70B)。注意力和规范保留在 GPU 上(~8 GB)。FFN 张量(~32 GB)从 NVMe 通过动态大小的池缓冲流出,带有扩展的预取前瞻。
池缓冲大小、预取深度和内存预算从硬件配置文件中自动计算 —— 无需手动调整。
所有基准测试均在 M1 Max、32 GB 统一内存、~5.1 GB/s NVMe 顺序读取上进行。
关键要点:对于适合内存的模型,Hypura 不增加任何开销。对于不适合内存的模型,Hypura 是"能运行"和"会崩溃"的区别。Mixtral 上的 Expert-streaming 通过仅将非专家张量保留在 GPU 上并利用 MoE 稀疏性(每个 token 仅 2/8 专家激活),实现可用的交互式速度。Dense FFN-streaming 将这扩展到非 MoE 模型,如 Llama 70B。池大小和预取深度随可用内存自动扩展。
Hypura 从源代码用 Cargo 构建。你需要 Rust 1.75+ 和 CMake(用于供应的 llama.cpp)。
git clone --recurse-submodules https://github.com/t8/hypura.git
cd hypura
cargo build --release
二进制文件位于 target/release/hypura。
Homebrew tap 即将推出。
# 对硬件进行分析(仅运行一次,缓存)
hypura profile
# 在 GGUF 模型上运行推理
hypura run ./model.gguf --prompt "Hello, world"
# 交互式聊天
hypura run ./model.gguf --interactive
# 基准测试:Hypura 调度 vs 朴素基线
hypura bench ./model.gguf
# 检查模型放置计划而不加载
hypura inspect ./model.gguf
在未测试的模型上首先使用 --max-tokens 10 开始,然后再扩大规模。
Hypura 公开了 Ollama 兼容的 HTTP API,使其成为任何与 Ollama 通信的工具的替代品 —— 包括 OpenClaw。
hypura serve ./model.gguf
# Hypura serving Mixtral 8x7B Instruct v0.1
# Endpoint: http://127.0.0.1:8080
# Ollama-compatible API: /api/generate, /api/chat, /api/tags
通过在 ~/.openclaw/openclaw.json 中设置 Ollama 基础 URL,让 OpenClaw 指向 Hypura:
{
"models": {
"providers": {
"ollama": {
"baseUrl": "http://127.0.0.1:8080",
"api": "ollama"
}
}
}
}
openclaw config set models.providers.ollama.baseUrl "http://127.0.0.1:8080"
Hypura 使用原生 Ollama 协议(/api/chat 与 NDJSON 流式传输),因此不需要兼容性垫片。
hypura serve <MODEL> [OPTIONS]
Options:
--host <HOST> 绑定到的主机 [default: 127.0.0.1]
--port <PORT> 绑定到的端口 [default: 8080]
--context <N> 最大上下文长度 [default: 4096]
Hypura 是一个 Cargo 工作区,有两个 crates:
hypura —— 主二进制文件和库。CLI 在 src/main.rs 中,所有逻辑在 src/lib.rs 模块中。
hypura-sys —— 到 llama.cpp 的 FFI 绑定(在 vendor/llama.cpp/ 供应,通过 CMake 构建)。
不会。Hypura 仅在推理期间从你的 SSD 读取 —— 它从不写入。
SSD 损耗由写入周期(NAND 闪存单元的编程/擦除周期)引起。读取不会降低闪存单元。Hypura 的整个 NVMe I/O 路径使用仅读 pread() 调用和 F_NOCACHE,将张量权重从 GGUF 文件流式传输到 RAM/GPU 内存池,其中所有计算都发生。SSD 被用作冷存储,而不是工作内存。
Hypura 执行的唯一写入可以忽略不计:基准测试结果 JSON 文件(~KB)、共同激活统计信息(~KB 到 ~/.hypura/),以及如果你选择运行的一次性 hypura optimize 命令。正常推理产生零 SSD 写入。
bench --baseline 在模型超过 RAM 减 4 GB 余量时被阻止。使用 --force 来自行覆盖。
始终在未测试的模型上从 --max-tokens 10 开始。
测试模型应放在 ./test-models/(未检入)。
我有道德义务说,我没有自己编写这个存储库中的代码。这个项目是探索使用 LLM 根据我的指导执行任务。我用来达到这一点的大多数 prompt 都是使用苏格拉底方法、真正的好奇心和一种直觉推导出来的,即 NVMe 支持推理是未充分利用的,尽管它是(缓慢但)完全有效的内存形式。