vLLM 发布 vllm-metal:在 Apple Silicon 上并发服务
Announcing vllm-metal: Concurrent Serving on Apple Silicon
vLLM 官方发布 vllm-metal,把 vLLM 的 V1 调度器、分页 KV cache 和 OpenAI 兼容服务带到 Apple Silicon,执行层由 MLX 与 Metal 承担。
vLLM 官方把调度器、分页 KV cache 和 OpenAI 兼容服务搬到 Apple Silicon,并给出与 llama.cpp、oMLX 的并发实测对比。
在 Mac 上进行本地推理很简单,直到多个请求重叠。此时,首 token 时间(TTFT)、内存增长和准入控制就变成了服务问题,而不是模型执行问题。vllm-metal 将 vLLM 的调度器、分页 KV 缓存和 OpenAI 兼容服务器带到 Apple Silicon,由 MLX 和 Metal 负责执行。
我们的首个正式版本 v0.28.0 将 vllm-metal 的版本号与上游 vLLM 对齐。它引入了批量多 token 预测(MTP)、GGUF 和混合模型支持,并在 M5 上实现了更快的预填充。你可以通过 Homebrew 安装 v0.29.0。
vllm-metal 接入上游 vLLM。vLLM 提供 V1 调度器、分页 KV 块管理、分块预填充、采样,以及支持流式传输和工具调用解析的 OpenAI 兼容前端。mlx_lm 提供模型实现;MLX 负责执行它们。
在模型层面,vllm-metal 原样复用 mlx_lm 的权重加载、RMSNorm、线性层、MoE 和 MLP 层。这些层独立处理每个 token,因此它们在打包的 token 轴上运行,无需知道请求边界。注意力机制确实需要这些边界,因此 vllm-metal 用分页 varlen Metal 内核替换了原生注意力。因此,该插件的大部分模型特定代码都集中在一个层中。
启动 OpenAI 兼容服务器
在搭载 macOS 15 或更高版本的 Apple Silicon 上,使用 Homebrew 安装稳定版:
brew tap vllm-project/vllm-metal https://github.com/vllm-project/vllm-metal
brew install vllm-project/vllm-metal/vllm-metalHomebrew 管理 Python 和依赖项。直接运行 vllm 即可启动模型:
# --gpu-memory-utilization sets the serving memory budget; see below.
vllm serve Qwen/Qwen3.5-0.8B --gpu-memory-utilization 0.5
# 64 GB Macs: the 27B hybrid
# vllm serve mlx-community/Qwen3.8-27B-4bit --gpu-memory-utilization 0.7
# Speculative decoding: Gemma 4 with its MTP assistant
# vllm serve google/gemma-4-E4B-it --gpu-memory-utilization 0.5 \
# --max-model-len 16384 --no-async-scheduling \
# --speculative-config '{"method":"mtp","model":"mlx-community/gemma-4-E4B-it-assistant-bf16","num_speculative_tokens":1}'该服务器支持 OpenAI API:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "Qwen/Qwen3.5-0.8B",
"messages": [{"role": "user", "content": "Say hi"}]}'任何接受 OpenAI 兼容基础 URL 的工具都可以指向 http://localhost:8000/v1,包括编码代理;vLLM 文档涵盖了 Claude Code 和 Codex 的设置。
设置可预测的内存预算
vllm-metal 的内存保护允许你通过 --gpu-memory-utilization 设置推理预算,为 macOS 和你的应用留出余量。与上游 vLLM 一样,它在启动时运行预热过程,以计入模型权重、激活值和临时缓冲区,然后将剩余预算分配给固定的 KV 缓存。它还会限制 MLX 的可复用缓冲区缓存,以防止服务期间内存累积。无法放入 KV 池的请求会等待页面可用。
打包查询与分页 KV
在 mlx_lm 的填充批次中,注意力查询的形状为 [B, H, T_max, D]:每个请求都获得批次中最长的查询长度。MLX 的 scaled_dot_product_attention 没有 varlen 接口。
vllm-metal 保留了 vLLM V1 用于分块预填充和解码的统一模型步骤。它将每个已调度的查询 token 打包到 [total_q, H, D] 中,用 cu_seqlens 标记请求边界,并在一次模型前向中运行混合步骤。
KV 是独立的:mlx_lm 保持连续的 [B, H, T, D] 缓存,而 vllm-metal 将 KV 存储在固定大小的页面中,由每个请求的块表寻址。已准入的请求可以增长,而无需重塑填充缓存。
预填充和解码使用不同的批处理策略:
| 引擎 | 预填充注意力 | 解码批处理 | KV |
|---|---|---|---|
| Lily | 逐提示调用 | 单请求 | 连续 |
| Uzu | 逐提示调用 | 单请求 | contiguous |
| oMLX | per-prompt calls | batched | contiguous |
| Splash | per-prompt calls | batched, max 4 | paged |
| mlx_lm | padded | batched | contiguous |
| llama.cpp | mask over slots | batched + prefill | fixed cells |
| vllm-metal | packed, cu_seqlens | batched + prefill | paged |
Prefill attention 描述 prompt 查询如何进入 attention kernel:分别进入、填充到共同长度,或拼接。Decode batching 在一个模型步骤中处理多个请求;“+ prefill”表示该批次中包含 prompt token。KV 描述逻辑存储:连续缓冲区、单个 token 单元或 token 块。
在 4-bit 的 Qwen3.6-35B-A3B 上,我们比较了八个请求的批次,总 prompt token 约为 6,000,每个请求输出 20 个 token。批次 A 的 prompt 长度相近;批次 B 有一个较长的 prompt。表格报告批次墙钟时间(秒),prompt 长度已取整。
| 批次 | Prompt token | mlx_lm | oMLX | llama.cpp | vllm-metal |
|---|---|---|---|---|---|
| A | 750 × 8 | 4.52 | 7.32 | 5.29 | 3.87 |
| B | 3,000 + 430 × 7 | 10.99 | 7.22 | 5.33 | 3.64 |
| 变化 | +143% | −1% | +1% | −6% |
Packing 还能将参差不齐的工作保留在一个批次中。在投机步骤中,一个请求可能贡献一个 decode token,另一个贡献其最后一个 token 加上请求特定数量的草稿,还有一个贡献一个 prefill 块。一个 [B, H, T_max, D] 查询张量必须要么将这些行填充到共同宽度,要么将它们拆分到多次前向中。vllm-metal 则将这些窗口拼接为 [total_q, H, D],并在一次目标模型前向中验证它们。
Metal kernel 将 vLLM 的统一 Triton kernel(在 The Anatomy of a Triton Attention Kernel 中描述)移植到 Apple GPU,甚至包括每个 threadgroup 对 cu_seqlens 运行的二分查找,以找出其查询 token 属于哪个请求。
agent 负载下的并发服务
多个编码 agent 或会话可以同时发送模型请求。我们使用 SiliconBench 的 agent split 对此进行了测量:100 个多轮 prompt,每个约 4.6K 输入 token,在配备 64 GB 内存的 M5 Pro 上运行。所有三个模型比较均使用 4-bit 权重。
SiliconBench 论文对九种 Apple Silicon 服务引擎提供了更广泛的评估,涵盖速度、内存使用和输出保真度。
每个并发级别都从全新服务器开始。oMLX 展示了其默认 SSD 缓存和仅 RAM 缓存;两者都从空开始。附录给出了服务配置,图注报告了完成数量。
Qwen3.8-27B
vllm-metal 在并发 2 和 4 时具有最低的 TTFT 和端到端延迟。启用 SSD offload 的 oMLX 在并发 1 时领先。
Gemma 4 E4B
对于 Gemma 4 E4B,我们将扫描扩展到并发 16,并纳入带有 MTP drafter 的 vllm-metal。
vllm-metal 在整个扫描过程中保持低 TTFT。llama.cpp 使用其默认的四个服务器槽位。
Qwen3.6-35B-A3B
Qwen3.6-35B-A3B 总参数为 35B,每个 token 激活 3B。它结合了混合专家层与标准 attention 和 gated-delta-net (GDN) 线性 attention。
在并发数为 4 时,vllm-metal 和带 RAM 缓存的 oMLX 在吞吐量和端到端延迟上接近,TTFT 差距更大。mlx_lm 的曲线只覆盖了它完成的一小部分请求。
并发负载下的批量 MTP
MTP 使用辅助模型起草 token,目标模型在连续批次中对其进行验证。下表比较了在 Gemma 4 E4B 上每步一个草稿 token 与不使用 MTP 的生成:
| 并发数 | 墙钟时间 vs. 无 MTP | 输出 tok/s vs. 无 MTP | TTFT 平均值 vs. 无 MTP |
|---|---|---|---|
| 1 | −15% | +20% | −1% |
| 8 | −1% | +0% | +4% |
| 16 | −8% | +9% | +20% |
MTP 通过 --speculative-config 选择启用。Metal 路径目前支持 Gemma 4 的纯贪心采样(temperature=0)和同步调度(--no-async-scheduling)。
v0.28.0 中的其他功能
M5 上更快的预填充
在 M5 Mac 上,vllm-metal 会自动对兼容的预填充批次使用 NAX 注意力内核,该内核利用 GPU 的张量硬件。较早的 Mac 继续使用现有路径。此比较使用 Qwen3-0.6B:

在混合模型上复用对话历史
多轮智能体在每一轮都会重新发送其不断增长的对话的大部分内容。前缀缓存让下一轮可以复用为较早轮次计算出的块,而不是再次预填充完整历史。
对于 Qwen3.5 风格的混合模型,vllm-metal 支持 vLLM 的 align 模式。它在与注意力 KV 相同的块边界处保存 GDN 循环状态,使两者都能从缓存前缀恢复(PR #634)。此路径仍为实验性,尚不能与推测解码结合使用。
模型与服务功能
v0.28.0 还包括:
- LoRA 适配器、结构化输出,以及三种推测解码方法:Gemma 4 MTP、独立草稿模型和提示查找 n-gram。
- GGUF 检查点,包括用于本地 GGUF 权重的 Hugging Face 配置源。
- 来自 Qwen3.5、Qwen3.6、Qwen3.8 和 Qwen3-Next 系列的混合注意力模型。
- 通过 MLX ring 后端在多台 Mac 上进行流水线并行。
- 实验性视觉语言模型、文本嵌入与重排序,以及语音转文本。
支持的模型矩阵和功能指南见 vllm-metal 文档。
从 M1 Pro 到 M5 Pro 的同一套技术栈
我们在四台 Mac 上以并发数 1 和 8 运行了相同的 Gemma 4 E4B 4 位工作负载。
运行跨机器基准测试
每次运行使用 vllm bench serve,包含 100 条 Sonnet 提示,约 1,024 个输入 token 和 128 个输出 token,--gpu-memory-utilization 0.5,禁用前缀缓存,并为每个并发级别启动全新服务器。
在每台机器上使用 vllm-metal v0.29.0 和 vLLM 0.29.0。通过 Homebrew 安装稳定版:
brew tap vllm-project/vllm-metal https://github.com/vllm-project/vllm-metal
brew install vllm-project/vllm-metal/vllm-metal在一个终端中启动服务器:
vllm serve mlx-community/gemma-4-e4b-it-4bit \
--gpu-memory-utilization 0.5 \
--max-model-len 2048 \
--no-enable-prefix-caching \
--host 127.0.0.1 --port 8000在另一个终端中,下载 Sonnet 文本并运行客户端:
curl -fsSL https://raw.githubusercontent.com/vllm-project/vllm/main/benchmarks/sonnet.txt \
-o sonnet.txt
BENCH_MACHINE=m1pro-32gb
BENCH_CONCURRENCY=1
vllm bench serve \
--backend vllm \
--model mlx-community/gemma-4-e4b-it-4bit \
--base-url http://127.0.0.1:8000 \
--dataset-name sonnet --dataset-path sonnet.txt \
--sonnet-input-len 1024 --sonnet-output-len 128 \
--num-prompts 100 --num-warmups 3 \
--request-rate 10 --max-concurrency "$BENCH_CONCURRENCY" \
--temperature 0 --ignore-eos --seed 0 \
--save-result --result-dir benchmark-results \
--result-filename "${BENCH_MACHINE}-c${BENCH_CONCURRENCY}.json"对于并发数 8,用 Ctrl+C 停止服务器,用相同命令再次启动,并用 BENCH_CONCURRENCY=8 重新运行客户端代码块。在 M5 Pro 上,设置 BENCH_MACHINE=m5pro-64gb;为每台额外机器使用不同的名称。
结果保存在 benchmark-results/ 下。图表使用 Mean TTFT(毫秒除以 1,000 即为秒)和 Output token throughput。请在每个结果旁保留成功请求数。
附录:基准测试复现
跨引擎基准测试脚本和结果位于 SiliconBench。运行使用 vllm-metal 0.28.0.dev20260901062632 搭配 vLLM 0.28.0、llama.cpp 0eadefeb 和 oMLX dc312e6e。
运行在固定并发下以闭环方式使用贪心采样。结果涵盖已完成的请求;空响应计为失败。每个引擎使用各自的 4-bit 转换。内存设置为 vllm-metal 的默认值 --gpu-memory-utilization 0.92、oMLX 的平衡内存保护,llama.cpp 则没有显式上限。
服务配置与测量细节
为每个并发级别启动一个全新的服务器,并创建一个空的 oMLX 缓存目录,RAM 模式也不例外。
# llama.cpp
llama-server -m <model>.gguf --host 0.0.0.0 --port 8001 \
-ngl 99 --parallel 4 -c 65536
# vllm-metal
vllm serve <model> --host 0.0.0.0 --port 8004 \
--enable-prefix-caching --max-model-len 16384
# vllm-metal + MTP (Gemma only)
vllm serve <model> --host 0.0.0.0 --port 8004 \
--enable-prefix-caching --max-model-len 16384 --no-async-scheduling \
--speculative-config '{"method":"mtp","model":"mlx-community/gemma-4-E4B-it-assistant-bf16","num_speculative_tokens":1}'
# oMLX, SSD offload
omlx serve --model-dir <dir> --host 0.0.0.0 --port 8005 \
--paged-ssd-cache-dir <fresh-empty-dir> --paged-ssd-cache-max-size 100GB \
--hot-cache-max-size 0
# oMLX, RAM-only cache
OMLX_HOT_CACHE_ONLY=true omlx serve --model-dir <dir> --host 0.0.0.0 --port 8005 \
--paged-ssd-cache-dir <fresh-empty-dir> --paged-ssd-cache-max-size 100GB \
--hot-cache-max-size 8GB填充对比报告每个单元格两次全新服务器运行的中位数,每次运行包含八个请求,每个请求 20 个输出 token。
NAX A/B 使用 Qwen3-0.6B,输入 2,048 / 输出 32 个 token 以及输入 1,024 / 输出 128 个 token。每种配置以请求速率 10 和并发 32 运行 100 条 Sonnet 提示。
致谢
vllm-metal 基于 Apple MLX 团队的 MLX 和 mlx_lm、用于视觉语言路径的 mlx-vlm,以及 vLLM 的引擎和硬件插件接口构建。感谢上游 vLLM 维护者一路以来的审查和支持,也感谢所有针对 v0.2 和 v0.3 版本提交 issue 并分享基准测试的人。
来源:vLLM Blog · vllm.ai