gpu-passthrough-macos-vms.md
历史
gpu-passthrough-macos-vms.md
文件元数据与控件
Apple Silicon 与 macOS 虚拟机:llama.cpp 推理速度提升 11–16 倍
发布于 2026 年 8 月 11 日,作者:Francesco Bonacci 与 Johnny Franks
如果你从一开始就在关注 Cua,你或许还记得,它始于我们 macOS 虚拟化栈 Lume 在 Show HN 上的发布。
通过 Apple 的 Virtualization.framework 运行的 macOS 客户机,使用的是由宿主机 Apple GPU 支撑的虚拟 GPU。在我们标准的 Tahoe 虚拟机中,该设备报告的是保守的 Metal 能力配置。应用程序会依据这些配置来选择内核与渲染路径,这导致 llama.cpp 运行的是慢得多的 GPU 代码。
我们构建了一个小型的、进程级作用域的兼容层,它能为单个客户机进程修改部分能力配置的返回值,从而让 llama.cpp 能够选用更新的 Metal 内核。这是我们更广泛工作成果中的第一个结果,该工作旨在将 Lume 的虚拟化基础连接到 Cua Driver 背后的本地计算机使用环境,以及 Cua Cloud 和 Fleets 背后的基础设施。
我们今天以研究发布的形式放出这项工作,采用与 Lume 和 Cua 相同的宽松许可证,以便其他人能够复现这些结果,并帮助摸清哪些 Apple Silicon 芯片、macOS 版本和 Metal 工作负载能从中受益。
在 M1 Ultra 上,通过 llama.cpp 运行的 TinyLlama 1.1B,其提示词处理速度比同一标准虚拟机中的相同工作负载快 11.08 倍,token 生成速度快 16.36 倍。提示词处理达到了我们裸机结果的 98%。源码、构建脚本、能力探测程序和原始基准日志均已包含在内,方便你查验并复现该结果。
我们用 Google 的 Gemma 4 12B QAT Q4_0(今年发布的 6.98 GB 模型)重复了该实验。同一兼容层将提示词处理速度提升了 7.20 倍,token 生成速度提升了 14.54 倍。解锁后的虚拟机达到了裸机提示词速度的 99.59%,以及裸机生成速度的 94.82%。
同样的能力差距也出现在其他 Virtualization.framework 前端中。Tart 是另一款 macOS 虚拟化 CLI,它有一个公开的 issue“macOS 客户机中无 GPU 透传?”,涵盖了 macOS 客户机内的图形与 LLM 性能问题。
macOS 虚拟机内部的限制
Apple 的 Virtualization.framework 会为 macOS 客户机提供一个虚拟图形设备。客户机通过一个专用 GPU 驱动程序提交 Metal 工作负载,而 Apple 的主机端栈会在物理 GPU 上执行这些工作。这种安排属于半虚拟化(paravirtualization),即主机端保留对硬件的控制权,客户机则使用一个能感知虚拟化的设备。
这与基于 QEMU 和 KVM 构建的其他虚拟化栈不同,后者可以采用不同的架构。在 x86 Linux 主机上,VFIO 可以通过 IOMMU 将兼容的物理 PCI 设备或硬件功能分配给虚拟机,让客户机直接访问该设备。这通常就是 GPU 直通(GPU passthrough)所指的模式。
在我们标准的 Tahoe 虚拟机中,半虚拟化设备报告的能力大约是 Apple 5 代系列,最大线程组内存为 32 KB,并且 SIMD 组矩阵支持不可用。现代 Metal 软件会利用这些返回值来选择内核,因此即使该设备能够执行更新的内核,llama.cpp 也还是选择了较慢的执行路径。
Apple 通过 GPU 系列(GPU families)和功能表来记录 GPU 能力,并建议在运行时查询设备。这使得所报告的能力边界变得至关重要:应用程序完全是在按照平台告诉它们的信息行事。
解决方案:一个进程作用域的 Metal 能力垫片(capability shim)
我们构建了一个小型 Metal 能力垫片(一种插入在应用程序与 API 之间的兼容层),它运行在单个客户机进程内部。它会拦截选定的 Metal 能力查询,并修改返回给该进程的答案。Metal 应用程序会利用这些答案来选择内核,因此返回经过测试的 Apple 系列和线程组内存值,可以让 llama.cpp 选择其更新的 GPU 路径。对于我们测试的配置,该垫片会:
- 将 `supportsFamily:` 的应答提升到 Apple family 9(1009);以及
- 将报告的最大线程组内存从 32 KB 提升到 64 KB。
这足以让所测试的 llama.cpp 构建版本选择更新的 SIMD 组归约(SIMD-group reduction)、SIMD 组矩阵(SIMD-group matrix)和 bfloat16 路径:
| 能力项 | 标准客户机 | 测试配置 |
|---|---|---|
| supportsFamily:1009 | false | true |
| SIMD 组矩阵 | 关闭 | 开启 |
| SIMD 组归约 | 关闭 | 开启 |
| bfloat16 | 关闭 | 开启 |
| 最大线程组内存 | 32 KB | 64 KB |
测试所用的配置文件修改了两个上报值:Apple 系列标识和线程组内存上限。Common、Mac、Metal 和工作集大小等值在基准测试期间保持默认设置。我们移除了原始研究钩子中的私有功能配置文件钩子、时钟和计时插桩、网格替换、光线追踪覆盖、参数布局保护以及管线编译回退。其源码足够小,便于审计,且格式错误或缺失的配置会使进程保持在其默认能力路径上。
该工作负载仍走 Apple 的 Virtualization.framework 图形路径,并在宿主的 Apple GPU 上执行。能力变更仅限于注入的客户机进程范围内。
物理 GPU 分配、裸 PCI 或 VFIO 直通以及内核修改均不属于此机制范畴。上报的系列标识描述的是我们测试所覆盖的路径;每新增一个 Metal API 都需要单独验证。
该垫片在 Apple 现有的虚拟 GPU 路径上解锁了 Metal 能力。虚拟机用户通常以“GPU 直通”之名遇到这一更广泛的限制。
最小化工件的最新结果
我们在配备 48 核 GPU 的 Apple M1 Ultra 和 macOS 26.6.1 上进行了测试。客户机为运行在 Lume 0.5.1 中的当前公开 Tahoe Cua 镜像(macOS 26.5.2、8 vCPU、16 GiB)。三次运行均使用官方 llama.cpp b10167 版本和相同的 TinyLlama 1.1B Chat Q4_K_M 模型。
命令为:
llama-bench -m tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf \ -p 512 -n 128 -r 10 -t 8 -ngl -1 -o json
以下数值为每个基准测试行所输出的十个样本的中位数:
| 工作负载 | 裸机宿主 | 默认客户机 | 解锁客户机 | 客户机加速比 | 解锁 / 宿主 |
|---|---|---|---|---|---|
| 提示词处理,512 tokens | 4,871.99 tok/s | 431.86 tok/s | 4,786.70 tok/s | 11.08× | 98.25% |
| Token 生成,128 tokens | 286.71 tok/s | 12.63 tok/s | 206.60 tok/s | 16.36× | 72.06% |
提示词处理几乎达到了宿主结果。生成速度达到宿主的 72.06%,仍存在可测量的虚拟机差距。该收益取决于宿主 GPU、客户机版本、应用程序和工作负载形态。
TinyLlama 的原始结果和环境记录包含确切的镜像摘要、模型和二进制哈希、命令、JSON 输出、stderr 以及校验和。这些候选发布结果验证了本文中使用的精简垫片。
一个当前的 12B 模型
TinyLlama 之所以能成为一个有用的受控基准,是因为它运行速度快,并且能清晰地暴露 Metal 路径。我们还想要一个开发者如今可能会选择的更大模型,因此我们通过同一个 llama.cpp 二进制文件,运行了 Google 官方的 Gemma 4 12B 指令微调版 QAT Q4_0 GGUF 模型。
主机、虚拟机、shim、基准测试形态以及十次采样方法均保持不变。我们禁用了推测解码,并保持多模态投影器未加载,从而将对比保持在同一条 Metal 推理路径上:
| 工作负载 | 裸机主机 | 原版虚拟机 | 解锁虚拟机 | 虚拟机加速比 | 解锁版 / 主机 |
|---|---|---|---|---|---|
| 提示词处理,512 tokens | 517.88 tok/s | 71.66 tok/s | 515.76 tok/s | 7.20× | 99.59% |
| Token 生成,128 tokens | 52.38 tok/s | 3.41 tok/s | 49.67 tok/s | 14.54× | 94.82% |
Gemma 4 的证据将 Google 的模型修订版本和 SHA-256 与最终的原始采样数据一并固定下来。在检测到另一个主机计算工作负载后,我们丢弃并重新运行了一个初步的原版系列测试。保留的原版、解锁版和裸机文件均来自同一个无争用时段,并显示出紧密的采样范围。
我们还在 MLX 0.32.0 上测试了 MLX-LM 0.31.3 与 mlx-community/Llama-3.2-3B-Instruct-4bit 的组合。性能保持平稳,因为 MLX-LM 在原版虚拟机中已经很快了:
| 工作负载 | 原版虚拟机 | 解锁虚拟机 | 比率 |
|---|---|---|---|
| 提示词处理,512 tokens | 1,656.55 tok/s | 1,665.47 tok/s | 1.005× |
| Token 生成,128 tokens | 172.09 tok/s | 170.86 tok/s | 0.993× |
这个平稳的结果有助于定义发布配置。在消融测试期间,通告 MTLGPUFamilyMetal3 使得 MLX 请求了一个通过半虚拟化设备无法获得的驻留集。发布版 shim 将更改后的答案限制在 Apple 家族的枚举值内,并保持 Metal 3 为其原版值。相关的 MLX 分支可以在其 Metal 驻留实现中看到。
这与 Apple 平台的关系
这完全在 Apple 硬件上运行,通过 Apple 随 Virtualization.framework 提供的半虚拟化 GPU 路径实现。该 shim 仅影响一个虚拟机进程读取的选定值。主机、虚拟机内核、其他虚拟机进程、内容保护状态和许可状态均保持其现有配置。
该技术依赖于客户机 Metal 实现中私有的、随版本变化的行为。Apple 可能会在 macOS 版本之间更改它,因此我们独立测试每个主机和客户机的组合。不受支持的方法会让进程保持其默认路径,而每个额外的 API 都需要自己的虚拟化测试。
我们欢迎 Apple 就半虚拟化图形不受限制功能级别的预期行为和支持性做出澄清。从事 Metal 或 Virtualization.framework 工作的 Apple 工程师可以通过 vz@trycua.com 联系我们。
在 Lume VM 中试用
源代码位于 libs/lume/metal-capability-shim。构建并验证两个特定于架构的 dylib:
cd libs/lume/metal-capability-shim
./Scripts/build.sh
./Scripts/verify.sh 停止 VM,为您的 macOS 用户启动的 VM 启用不受限制的功能级别,然后重新启动:
lume stop my-vm
defaults write com.apple.gpusw.ParavirtualizedGraphics \
ForceUnrestrictedDeviceFeatureLevel -bool true
lume run my-vm 将匹配的 dylib 以及探针或工作负载复制到客户机中,然后将激活范围限定到该进程:
lume ssh my-vm \ "DYLD_INSERT_LIBRARIES=/path/to/LumeMetalCapabilities-arm64.dylib \ LUME_METAL_APPLE_FAMILY_MAX=1009 \ /path/to/metal-capabilities 1009"
对于长时间运行的推理服务器、渲染器或工作进程,请使用按工作负载划分的 LaunchAgent。在该工作负载的环境中设置 DYLD_INSERT_LIBRARIES,以便登录会话保持默认状态。Lume 指南提供了完整的模板、校验和与验证步骤,以及回滚说明。
移除环境变量并重新启动工作负载即可使其恢复默认行为。要恢复主机偏好设置,请停止 VM,删除 ForceUnrestrictedDeviceFeatureLevel,然后再次启动 VM。
限制
- 实验性且随版本变化。该 shim 使用了私有的客户机 Metal 实现细节,这些细节可能在任何 macOS 版本中发生变化。
- 按进程生效。它仅影响被注入的工作负载及其子进程;经过加固或受平台保护的二进制文件可能会拒绝库注入。
- 配置的能力配置文件。它报告了我们测试所涵盖的 Apple 系列值。物理 GPU 能力发现不在其范围内。
- 验证范围有限。当前证据涵盖了在所列 M1 Ultra 主机和 Tahoe 客户机上对能力探针、两个 llama.cpp 工作负载以及一次 MLX-LM 兼容性运行的测试。其他芯片、客户机版本、模型和 Metal API 需要单独测试。
- 仍然是 VM。现有的 Virtualization.framework 渲染和虚拟化限制仍然存在。
总结
这位访客的保守回答背后,隐藏着一条出人意料的强大 GPU 路径。在我们的测试机器上,两项范围狭窄的能力变更将 TinyLlama 的提示词处理速度从每秒 432 个 token 提升到了每秒 4,787 个 token。对于 Gemma 4 12B,提示词处理速度从每秒 71.66 个 token 提升到 515.76 个 token,生成速度则从每秒 3.41 个 token 提升到 49.67 个 token,而工作负载始终保持在 Apple 现有的 GPU 桥上。
Lume 最初是为了让 macOS 虚拟机对开发者更实用而创建的。这一结果为我们提供了在更多 Apple Silicon 代际、客户机版本和 Metal 工作负载上进行测试的基础。
想帮忙吗?在 GitHub 上给 Cua 加星标,并在你的设备上测试这个 shim。如果你遇到问题,请提交 issue,并附上你的宿主机芯片、宿主机和客户机版本、确切的工作负载,以及默认和解除限制后的结果。如果你验证了新的组合或改进了 shim,请发送拉取请求。

