Technical Whitepaper · Apple Silicon Inference

oMLX:把 KV 缓存 变成
可持久化的模型状态数据库

这不是一篇「Mac 上跑大模型」的入门介绍。它要回答一个更窄、更工程的问题: 当一个编码智能体每一轮都在重发两万多个 token 的、几乎不变的前缀时, 一台 Apple Silicon 机器上的推理服务应该长成什么形状? oMLX 给出的答案是把 KV 缓存从进程内的易失状态,改造成跨请求、跨重启的内容寻址存储层—— 其余所有设计(连续批处理、多模型池、Anthropic 原生端点、菜单栏应用)都是这个答案的配套。

对象:jundot/omlx · Apache 2.0 版本基线:0.6.x 系列实测 + 0.7.x 主干 资料截止:2026-10-26 证据分级:厂商自测 / 第三方受控 / 社区提交

00摘要:九条可证伪的判断

如果只读一页,读这一页。每条判断都标注了它在本报告中的证据位置,也标注了它可能在什么条件下失效。

3.36×
8 路并发吞吐提升
Qwen3.5-122B-A10B-4bit,M3 Ultra
68.6s → 2.5s
同一 16.7K 请求的 TTFT
第 2 次命中 16384 块(第三方实测)
4096 tok
前缀匹配的块粒度
不足一个块无法命中
20–22s
冷启动总预算
加载 + 首次推理预热
判断 01

oMLX 解决的不是「Mac 上能不能跑」,而是 Agent 工作负载的 prefill 税:把重复前缀的计算成本,用存储换掉。

判断 02

分层 KV 缓存的价值集中在冷路径的恢复,不在热路径的提速。与 mlx-lm 相比,热态几乎打平;差异只在重启之后才出现(7.2s vs 17s)。

判断 03

它的架构是 vLLM 的调度思想在 MLX 统一内存上的再实现(block / CoW / 前缀共享 / 内容寻址),而非 vllm-mlx 的封装。vllm-mlx 只是起点。

判断 04

连续批处理是 macOS 生态上真正稀缺的能力,但 3.36–4.14× 的 8 路加速比说明 decode 阶段被内存带宽锁死——并发是给多用户/多智能体的,不是给单用户提速的。

判断 05

在 oMLX 上,缓存命中率就是 SLA。命中率不由服务调优决定,而由客户端如何组装 prompt 决定:前缀顺序、tools 是否稳定、动态内容放在哪。

判断 06

在 Mac 推理栈里它不替代 Ollama。Ollama 卖易用性与模型库,oMLX 卖「智能体后端」这一格的工程属性,两者目标函数不同。

判断 07

能力上限由物理决定:它只把 KV 溢出到 SSD,不溢出权重。64GB 的机器买到的是「巨大上下文复用」,不是「更大的模型」。

判断 08

成熟度存在结构性矛盾:3465 次提交、近每日发版,修得快也破得快。生产使用必须锁版本 + 固定内核构建。

判断 09

正确的验收方式是 「看第 2 次请求,不看第 1 次」。任何用首轮 TTFT 做的评测,测到的都是冷启动税而不是缓存收益。

一句话结论

如果你的负载是「同一段大前缀被反复重发」——编码智能体、长系统提示、固定工具集、多轮会话——oMLX 是目前 macOS 上唯一把这件事当第一性问题来解的服务;如果你的负载是「单轮问答」或「一次性脚本」,它给你的只有冷启动的账单。

01定位判定:它是什么,以及它刻意不是什么

同名分支在 GitHub 上至少有四个,营销站点也在抢注相似域名。先把项目边界钉死,后面的所有性能讨论才有对象。

oMLX 是 jundot 维护的、Apache 2.0 授权的 Apple Silicon 本地推理服务。官方仓库的自我定位只有一句话:「带连续批处理与 SSD 缓存的 Apple Silicon LLM 推理服务器,可从 macOS 菜单栏管理」。支撑这一定位的是三层东西:一个 FastAPI 服务(暴露 OpenAI 与 Anthropic 兼容端点)、一套迁移自 vLLM 的分页缓存调度层、一个原生 SwiftUI 菜单栏应用。

它不是模型、不是微调框架、不是聊天客户端。它更接近于「把 vLLM 的工程模型搬到一个没有独立显存、没有 CUDA、没有多卡 NVLink 的硬件平台上」这一次移植尝试的产物。

1.1 起点与演化速度

项目于 2026 年 3 月在 Apple MLX 仓库的 GitHub Discussions 首次公开,起点是 vllm-mlx v0.1.0——一个把 vLLM 的 API 形状套在 MLX 后端上的薄封装。此后它长出了原创新实现:SSD 分层缓存、连续批处理、VLM 全分页缓存、Anthropic 兼容端点、原生 macOS 界面。仓库累计提交数已达 3,465,这个数字本身就是一条关于成熟度的重要证据(见 §09)。

v0.1.0 v0.4.4 v0.6.0 v0.6.1 v0.6.2 v0.7.x 2026-03 06-16 08-16 08-17 08-18 10 月 源自 vllm-mlx 多模型 + 面板 实验性分布式 双 prefill,32K +18.9% 稳定版基线 0.7.3.dev2 开发中 2026-08 的 72 小时内连发三个版本,是理解该项目节奏的关键样本
图 1 · 版本演化。0.6.0→0.6.2 三天内完成「实验性分布式 → 双 ANE/GPU prefill → 稳定版」三级跳;0.6.1 的双路径 prefill 在 Qwen3.8 上报告 32K 上下文 +18.9%、16K decode +34%(MTP 验证)。日期来自 Release 记录与第三方评测交叉核对。

1.2 与相邻项目的概念分野

最容易犯的错误,是把 oMLX 当作「Ollama 的 MLX 版」。两者共享的只有「本地 + OpenAI 兼容 API」这两点表面特征,目标函数完全不同:Ollama 优化的是首次可用时间(装完就能聊),oMLX 优化的是第 N 次请求的边际成本。

维度OllamaLM Studiollama.cppmlx-lmoMLX
推理后端llama.cpp;2026 起对支持模型默认走 MLX runtime(0.40+)llama.cpp + MLX,按模型择优GGUF / 自研 CPU+Metal kernelMLX 原生MLX + 可选原生 Metal 自定义内核
KV 缓存持久化仅内存,重启丢失仅内存prompt cache 落盘(非块粒度)内存 prompt cache三层:GPU / RAM / SSD
连续批处理串行,无真并发有限slot 级并发无调度器BatchGenerator + FCFS 调度
多模型同服可同时常驻可同时常驻单进程单模型单模型EnginePool + LRU + pin + TTL
API 面OpenAIOpenAIOpenAI-ishOpenAIOpenAI + Anthropic + Responses + MCP
平台mac / Linux / WinApple Silicon only几乎全平台Apple SiliconmacOS 15+ / Apple Silicon only
界面CLI成熟 GUICLICLI原生菜单栏 App + Web 管理面板
许可MIT闭源免费MITMITApache 2.0
表 1 · Mac 生态推理栈分野。oMLX 唯一无法被替代的格子是「KV 持久化 + 连续批处理 + Anthropic 原生端点」三者的交集。
判别式

一个推理服务属于「oMLX 这一类」,当且仅当同时满足三条:① 目标负载是智能体后端(长前缀、多轮、工具调用),而非聊天;② KV 缓存具备跨进程生命周期的持久层,而不只是进程内 LRU;③ 提供 Anthropic Messages 原生端点,使 Claude Code 一类客户端无需协议代理层。缺任一条,它解决的是另一个问题。

02理论根基:prefill 税,以及它为什么可以被存储掉

如果不理解 prefill 与 decode 的物理分野,就会把 oMLX 当成一个「更快的推理器」。它不是。它是一个「把已经算过的东西记住」的服务。

2.1 两个阶段的物理性质不同

Transformer 解码分两段,两段的瓶颈在硬件上是相反的:

这个分野直接决定了一件事:你能优化的只有 prefill,decode 你优化不动。这也解释了后文所有数字的形状——为什么 oMLX 的 TTFT 能从 68.6s 降到 2.5s,而 tok/s 始终在 40–60 徘徊。

2.2 智能体请求的真实形状

一次普通聊天请求的 prompt 可能是几百 token。而一次编码智能体请求是这样的:

[system prompt]        约 2k–8k tok   ← 几乎不变
[tool definitions]     约 3k–12k tok  ← 几乎不变,但顺序敏感
[项目上下文/RAG]       约 2k–20k tok  ← 会话内不变
[对话历史]             逐轮增长
[当前用户输入]         几百 tok       ← 唯一真正新增的部分

第三方实测给了一个具体样本:把 GitHub Copilot CLI 指向本地 26B 模型,每次请求都发送 26,627 个 token 的 system + tool 定义。第一轮 0 命中,耗时 1 分 28 秒;第二轮命中 14,330(54%),耗时降到 33 秒。同一个「2+2」,同样的模型,差 2.7 倍。

这就是 prefill 税:用户为了一段从未改变的文本,每一轮都重新付一次全量计算的账。

2.3 成本模型

把首 token 延迟(TTFT)拆开,可以得到一个能指导调优的式子:

T_ttft  =  T_load        # 模型权重进入统一内存(懒加载时发生)
         +  T_warmup      # Metal kernel JIT / MLX 图优化,首次推理独有
         +  N_uncached / R_prefill   # 未命中 token 数 ÷ prefill 速率
         +  T_book   # 块哈希查找、元数据、调度开销

四项里,只有第三项与缓存直接相关,而它的分子 N_uncached 完全由客户端的 prompt 组装策略决定。这是本报告最重要的一条推论:在 oMLX 上,性能调优的主战场不在服务端配置,而在调用方的 prompt 结构。

理论落地 · 四条可执行动作

① 静态在前,动态在后。system → tools → 稳定上下文 → 历史 → 当前轮。任何把时间戳、随机 ID、变量内容塞进 system prompt 的做法,都会让缓存永久失配。

② tools 顺序必须冻结。实测中仅交换两个 tool 的定义顺序,命中率从 16,384 块直接掉到 0,TTFT 从 2.5s 回到 81.4s。

③ 一次性调用主动放弃写缓存。摘要器、标题生成、分类器这类不重复的调用,用请求头 X-oMLX-Cache-Store: off 跳过存储,避免挤掉多轮会话真正要复用的前缀。

④ 客户端超时必须大于冷 prefill。如果客户端 30s 就断开,而冷 prefill 需要 68s,则缓存永远建不起来——每一轮都是冷启动,这是最隐蔽的死循环。

2.4 为什么 KV 可以持久化:一个结构性红利

因果注意力有一个性质:位置 i 的 KV 只依赖位置 0..i 的 token,与后面的内容无关。这意味着 KV 是「前缀的函数」——只要前缀相同,产出的 KV 就完全相同。

于是 KV 可以被当作内容寻址的数据来处理:给定前缀,算出它的哈希,去查有没有存过;存过就直接取回,没存过就算一遍再存。这一步跨过去,KV 就从「一次会话的临时产物」变成了「可索引、可去重、可落盘、可跨进程共享的模型状态数据库」。oMLX 的全部设计都是这句话的技术展开。

反过来说,这个红利有严格前提:前缀必须逐 token 完全一致。不是语义一致,不是「差不多」,是二进制一致。这个苛刻条件,就是后面所有工程细节(块大小 4096、链式哈希、tools 敏感)的来源。

03系统架构:五层,以及一条被刻意保留的窄通道

oMLX 的架构图不复杂,但每一层都对应一个 Apple Silicon 特有的约束。读这张图的正确方式,是问「这一层如果不这么设计,会死在哪里」。

API 层 FastAPI OpenAI 兼容 /v1/chat/completions Anthropic 原生 /v1/messages Responses + MCP /v1/responses 分词 / 嵌入 / 重排 tokenize, embed, rerank 引擎池 EnginePool LRU · pin · TTL BatchedEngine 文本 LLM VLMEngine 视觉 / OCR EmbeddingEngine 向量化 RerankerEngine 重排序 DecisionEngine 类型化决策 内存治理 Enforcer ProcessMemoryEnforcer 加载前预检总内存 · 默认上限 = 系统 RAM − 8GB · 内存守卫档位 safe / balanced 调度 Scheduler FCFS 队列 可配置并发数(默认 8) mlx-lm BatchGenerator 连续批处理 · 请求完成即退出批 缓存栈 核心差异点 PagedCacheManager BlockAwarePrefixCache Hot Cache(RAM) PagedSSDCacheManager L1 · 块表 / 引用计数 / CoW 链式哈希 · 前缀匹配 L2 · OrderedDict LRU L3 · safetensors 落盘 MLX · Metal · 统一内存 可选原生自定义内核:GLM-5.2 融合 DSA prefill / MiniMax M3 / Qwen3.5 · TurboQuant 3–4bit KV 硬件底座
图 2 · 五层架构。从 API 到 Metal 之间没有中间商,但每一层都插入了一次决策:谁来决定模型是否加载(内存治理)、谁来分配请求(FCFS)、谁来复用计算结果(缓存栈)。ProcessMemoryEnforcer 不在最底部而在引擎池下方,是为了在加载发生前就拒绝,而不是加载失败后再回滚。

3.1 五个层次各自的「不这么设计会死在哪里」

层组件如果缺省,故障形态
API/v1/chat/completions、/v1/messages、/v1/responses、/v1/mcp/toolsClaude Code / Codex 这类客户端强制走 Anthropic 或 Responses 协议,缺原生端点就得外挂代理(LiteLLM、claude-code-router),多一跳延迟与一处维护点。
引擎池Batched / VLM / Embedding / Reranker / Decision 五类引擎RAG 场景要同时跑 chat 与 embedding,单模型服务就得起两个进程、两份 Python 运行时、两次 Metal 上下文初始化,统一内存被重复占用。
内存治理ProcessMemoryEnforcermacOS 的统一内存被 GPU 与系统共用。加载一个超限模型不会优雅失败,而是把系统拖进 swap,整机卡死——这是 Apple Silicon 上最典型的「服务把机器搞崩」形态。
调度FCFS + BatchGenerator无调度器时请求线性排队(Ollama 现状)。多智能体并发时总吞吐不增反降,因为每个请求都要独立跑完整 prefill。
缓存栈Paged / Hot / SSD 三层 + 前缀匹配进程重启即丢全部上下文。智能体重连一次,等于把整段历史重新 prefill 一遍。这是 oMLX 存在的唯一理由。
表 2 · 架构层的功能归因。注意「内存治理」的位置:它必须挡在加载之前,位置错误会让保护动作变成事后补救。

3.2 被刻意保留的窄通道:自定义 Metal 内核

在对 Qwen3.5、GLM-5.2、MiniMax M3 这几族模型上,oMLX 提供可选的原生自定义内核(如 GLM-5.2 的融合 DSA prefill)。这不是通用优化,而是一条按模型手工开的窄通道,代价与收益都极端:

收益端

GLM-5.2 在 M3 Ultra 上,融合 DSA prefill 实测 845 tok/s,通用回退路径约 29 tok/s——将近 30 倍。这不是调参能补的差距。

代价端

需要完整 Xcode 与 Metal toolchain(xcodebuild -downloadComponent MetalToolchain)。只有 Command Line Tools 会报 xcrun: unable to find utility "metal"。缺失时不会报错,而是静默回退到慢路径,同时占用更多内存。

最容易被忽略的部署陷阱

「静默回退」意味着你无法从服务日志判断自己跑在快路径还是慢路径上。部署后必须显式验证:python -c "from omlx.custom_kernels import native_kernel_status; print(native_kernel_status())"。DMG 安装包已预编译内核,源码安装则需 make kernels;pip install -e . 会跳过内核构建。

04三层 KV 缓存:一个内容寻址的状态数据库

这是整份白皮书的技术核心。如果只能记住一句话:oMLX 把 KV 当作「前缀的哈希映射」来管理,而不是当作「这次请求的临时张量」。

4.1 三级存储与各自的职责

常见的误解是「三层缓存是三级速度」。实际上三层的分工并不对称——L1 管的是元数据与活跃性,L2 管的是避免重复读盘,L3 管的是跨重启的持久性:

层物理位置承载实体实际职责失效后果
L1GPU / 统一内存PagedCacheManager块表、引用计数、活跃请求映射、写时复制。在「纯 SSD」模式下只留元数据,数据全在盘上。活跃批次无法生成
L2宿主 RAMPagedSSDCacheManager 的 Hot Cache一个 OrderedDict 实现的 LRU 旁路缓冲,防止最近用过的块反复读盘。命中但延迟升高(每次命中都变成一次磁盘读)
L3SSDPagedSSDCacheManager以 .safetensors 持久化块,哈希分桶子目录;后台写线程 + 原子 rename + 磁盘配额。进程重启后全部重算(退化为普通服务器)
表 3 · 三层缓存的非对称分工。L2 的存在意义常被低估:没有它,SSD 命中的代价会逼近一次短 prefill。
请求 prompt token 序列 链式块哈希 · 每 4096 token 一块 SHA-256(model, parent_hash, tokens, extra_keys) BlockAwarePrefixCache 逐块查找 L1 活跃块 引用计数 > 0 已在 GPU,零 I/O → 直接复用 L2 Hot Cache OrderedDict LRU 内存命中 → 拷回 + 提升 L1 L3 SSD .safetensors 读盘 哈希分桶目录 → 回填 L2 / L1 未命中 重新 prefill 该块 store_cache 落 L2 → 后台异步写 L3 命中块只重算「最后一个命中点之后的增量」;未命中块的写入走有界队列,不阻塞推理主循环
图 3 · 命中路径。三条命中路径的代价量级完全不同:L1 是零 I/O,L2 是一次内存拷贝,L3 是一次随机读 + safetensors 解码。第 4 条路径(未命中)才是 prefill 成本真正发生的地方——也是唯一会写回存储的地方。

4.2 链式哈希:为什么不能只用 token 前缀的简单哈希

compute_block_hash(model_name, parent_hash, token_ids, extra_keys) 返回 SHA-256,关键在于 parent_hash 这一项:每个块的哈希都把它前面所有块的哈希卷进去,形成一条加密链。

这不是为了安全,是为了正确性。若只对块内 token 求哈希,那么「块 A 后面接块 B」和「块 C 后面接块 B」会因为块 B 内容相同而共享同一份 KV——但它们的注意力上下文完全不同,KV 也不同,复用直接产出错误输出。链式结构从机制上排除了这种可能:前缀不同,哈希必然不同。

代价是级联失效:只要有一个块变了,它之后所有块的哈希全变,全部失效。这解释了实测中最刺眼的那个数字——仅仅交换两个 tool 定义的顺序,命中块数从 16,384 归零。改动发生在很前面,后面 12 个块跟着一起死。

4.3 异构层:不是所有 KV 都能切块

MLX 的缓存类型并不统一。标准注意力层产出可切的 KVCache,滑动窗口模型产出 RotatingKVCache,而 Mamba / Jamba 这类状态空间层用的是 ArraysCache——它没有序列维度的分块语义,无法按块切片存储。oMLX 用一套类型注册表分流:

类别典型类名存储策略
可切片KVCache、QuantizedKVCache按块存为 4D slice,标准分页路径
旋转窗口RotatingKVCache、PrefillReadyRotatingKVCache专用 handler 处理窗口回绕,单独计算有效范围
不可切片ArraysCache(Mamba / Jamba)走 BoundarySnapshotSSDStore:在块边界处做整状态快照,序列化为原始字节由后台线程 flush;快照完成后立即释放 GPU 内存
表 4 · 缓存类型注册表。混合架构模型(注意力 + 状态空间)是这里最麻烦的一类,实测中 4096 的块粒度限制就来自这一类模型的架构约束。

BoundarySnapshotSSDStore 这个设计值得单独说一句:不可切片层的状态在长 prefill 期间会持续累积占用 GPU 内存,如果不做快照,128K 上下文的 prefill 会直接把统一内存吃穿。它选择「到边界就落盘、落完立刻释放」,本质是把内存压力换成了磁盘 I/O——又一次「用存储买空间」。

4.4 工程细节:那些决定它能不能长期跑下去的东西

异步写入

磁盘序列化在后台线程完成,队列有界(_MAX_PENDING_WRITES)。队列满时丢弃写入而不是阻塞推理——这是正确的取舍:宁可少缓存一块,不能卡住生成。

原子落盘

自定义的 _write_safetensors_no_mx 走原子 rename,避免半写文件被后续请求当作有效缓存读入。分桶子目录防止单目录文件数爆炸。

配额策略

默认 auto = (空闲磁盘 + 已有缓存文件)的 50%,且预算只增不减,不会因缓存增长而反复收缩。也可用 --paged-ssd-cache-max-size 20GB 硬限。

损坏恢复

校验和验证 + Metal command buffer 检查,失败自动重试并降级到重算路径。缓存坏掉不会让服务挂,只会让它变慢。

可观测性

stats.py 记录命中/未命中比、写丢弃数、恢复事件数。调优时这几个计数比 tok/s 更有诊断价值。

选择性写入

X-oMLX-Cache-Store: off 只跳过存储,不跳过查找。一次性调用仍能复用已有前缀,只是不再污染缓存。

为什么这套设计值得认真对待

它把「进程重启」这个在本地推理里几乎每十分钟发生一次的事件,从语义上的灾难降级成了性能上的小挫折。对云端服务这是理所当然的,对个人机器上的推理服务,此前没人做过。这才是 oMLX 真正的原创点——不是更快的 kernel,而是让缓存活过进程生命周期。

05调度与批处理:3.36× 背后的物理上限

连续批处理常被讲成「并发让一切变快」。在 Apple Silicon 上这个说法只对一半——它对,但只在多请求场景对;而它之所以不更快,原因在内存带宽,不在调度器。

5.1 连续批处理到底批了什么

传统静态批处理(static batching)要求一批请求全部生成完才能释放批次。一个请求生成 20 token、另一个生成 2000 token,整批就被后者拖住,短请求的槽位在空转。

连续批处理(continuous batching)改为按迭代步调度:每生成一步,完成的请求立刻退出、新请求立刻补位。oMLX 通过 Scheduler(FCFS 队列,并发数可配,默认 8)驱动 mlx-lm 的 BatchGenerator 实现这一点。

这在云 GPU 上是标配,在 Mac 本地推理市场上不是。Ollama 的请求是串行的;mlx-lm 本身没有服务层调度器。oMLX 在 macOS 上是第一个提供真实并发的选项——这个「第一」有实际价值,但价值的上限由下面的数字决定。

0 60 120 180 240 生成吞吐 tok/s Qwen3.5-122B-A10B 4bit · MoE · 8 路加速 3.36× 56.6 190.2 Qwen3-Coder-Next 8bit · 8 路加速 4.14× 58.7 243.3 MiniMax-M2.5 8bit · 8 路加速 3.71× 34.0 126.3 GLM-5 4bit · 8 路加速 3.61× 16.7 60.3 1 路并发 8 路并发 4 个模型的加速比全部落在 3.36–4.14× 窄带内 —— 不是实现差异,是带宽上限
图 4 · 连续批处理吞吐(M3 Ultra 512GB,厂商自测,pp1024 / tg128,无缓存复用)。四个模型参数量差一个数量级、量化位宽不同、架构不同(MoE 与 dense),但 8 路加速比全部收在 3.36–4.14× 这一段窄带里。这种「跨模型不变性」是判断瓶颈性质的最强证据:如果瓶颈在调度器实现,不同模型不该如此一致。

5.2 为什么加速比是 3.4× 而不是 8×

decode 阶段每生成一个 token,都要把全部模型权重从统一内存搬到 GPU 计算单元。8 个并发请求共享同一份权重搬运——这是批处理收益的来源。但共享之后,瓶颈就变成了带宽本身:每步的时间被「权重字节数 ÷ 带宽」锁死,与批里有几个请求无关。

所以在理想情况下,8 路并发的吞吐上限是单路的 8 倍(一次搬运服务 8 个 token),但实际上:注意力计算量随批增大而增加、KV 读取量线性增长、调度与同步开销上升。最终落在 3.4–4.1×,意味着带宽利用率从单路的约 12% 提到了约 45%——已经是很有价值的改善,但距离线性很远。

工程推论

并发数是多租户指标,不是单用户指标。把 --max-concurrent-requests 从 8 调到 32,对单个用户自己的请求延迟没有帮助,只会让 32 个请求一起变慢、并抬高内存占用到可能触发模型驱逐。正确的用法是:一个人跑一个智能体 → 保持默认;一台机器服务一个团队 → 才调高。

5.3 FCFS 的代价:缺少优先级是设计选择,也是缺陷

Scheduler 用先来先服务,没有优先级、没有公平份额、没有抢占。这个选择的后果是典型的队头阻塞:

缓解手段只有工程侧的:把批处理任务指向独立的第二个实例、或通过模型别名 + Profile 把它们在引擎层面分开。这不是优雅方案,但对单机场景够用——真正需要优先级的场景,通常也已经在考虑多机了。

06性能实证:把厂商数字与受控实验分开看

这一节只做一件事:把不同来源的证据按强度排队,找出它们在哪些结论上一致、在哪些地方互相矛盾。因为 oMLX 的性能叙事里,最有价值的事实恰恰是一条「反直觉」的。

6.1 证据分级

强度来源类型样本可采信范围
受控实验同机、同模型、同请求,改单一变量oMLX 0.6.4 + Qwen3.8-27B-4bit,M4 Max缓存机制的因果效应(命中/未命中差异)
受控实验同机对照 + 重启验证mlx-lm vs oMLX,M4 Max 64GB「持久化带来的差异」这一具体命题
第三方实测单机单场景,参数公开M1 Max 64GB(0.3.9.dev2)、M3 Max 128GB冷启动税、Copilot CLI 命中率
厂商自测官方基准表M3 Ultra 512GB,四个模型吞吐上限与批处理加速比
社区提交用户上传,参数不完全可控omlx.ai/benchmarks(M4 Max 40c 128GB 等)硬件档位的量级参考
表 5 · 证据分级。注意「厂商自测」用的 M3 Ultra 512GB 是市售产品的天花板配置,直接外推到 MacBook 会系统性高估。

6.2 决定性实验:同一个请求,跑两遍

最有说服力的一组数据来自一台 M4 Max 上的 0.6.4 版本 + Qwen3.8-27B-4bit。实验设计极简:同一个请求,连发两次,只观察变化。

① 首次请求(缓存全空) 命中 0 块 / 重算 5 块 block 0system block 1tools block 2tools block 3context block 4query ② 追加一轮历史 命中 4 块 / 只重算 1 块 → TTFT 68.6s → 2.5s block 0命中 block 1命中 block 2命中 block 3命中 block 4新增 322 tok ③ 只交换两个 tool 的定义顺序 命中 0 块 → TTFT 回到 81.4s block 0仍命中 block 1失配 block 2级联失效 block 3级联失效 block 4级联失效 链式哈希把「块 1 变了」放大成「块 1 之后全废」。这是 oMLX 命中率对 prompt 结构极其敏感的根本原因。
图 5 · 块级命中状态(示意结构 + 实测数字)。块内容为真实前缀的语义划分,实测数据来自 0.6.4 + Qwen3.8-27B-4bit / M4 Max,块粒度 4096 token。第 ③ 行是本报告最重要的工程警示:只改顺序、不改内容,缓存全部作废。

这组数字的完整形态是:

场景命中块数重算 tokenTTFT
相同请求(冷,缓存空)016,70068.6 s
相同请求(第 2 次)16,3843222.5 s
仅交换 tools 顺序016,70081.4 s
稳定前缀 + 逐轮增长(模拟智能体)12,288增量5–12 s
表 6 · 受控实验结果(第三方实测,oMLX 0.6.4 / Qwen3.8-27B-4bit / M4 Max)。注意「仅交换 tools 顺序」的 81.4s 比冷启动 68.6s 更高 —— 因为该次运行还叠加了其他会话干扰,属单次观测,不应解读为「交换顺序比冷启动更慢」。

6.3 反直觉的发现:热路径几乎打平,差异只在重启后

一位评测者在 M4 Max 64GB 上做了 mlx-lm 与 oMLX 的对照,最初的假设是「分层缓存会在重复长前缀上碾压 mlx-lm」。结果假设被推翻:

热态 prefill

3.4s(mlx-lm)vs 3.6s(oMLX)

基本平手。原因很简单:mlx-lm 在内存里也缓存 prompt。

decode 吞吐

22–25 tok/s(两者一致)

符合预期——decode 由带宽决定,与服务层无关。

重启后恢复

17s(mlx-lm)vs 7.2s(oMLX)

这才是差异真正出现的地方。

这条发现改变了整个评价框架

如果 oMLX 的价值主张是「更快的 prefill」,它在热路径上就输了——因为 mlx-lm 早就做到了。它真正独有的是让缓存活过进程死亡。所以正确的价值陈述不是「oMLX 更快」,而是「oMLX 在重启后依然快」。这两个说法指向完全不同的使用场景:前者是性能优化,后者是可用性工程。

6.4 冷启动税:必须预付款的 20 秒

oMLX 采用懒加载模型。这意味着服务启动后第一次请求要一次性付清三笔账:

0306090 TTFT 秒 首次请求(缓存全空) 68.6 相同请求第 2 次 2.5 仅交换 tools 顺序 81.4 稳定前缀逐轮增长 5–12 另据 M1 Max 64GB 实测:卸载 0.9s + 加载 9.7–11.9s + 首次推理预热 9–10s ≈ 冷启动总预算 20–22s 预热开销疑似来自 Metal 内核 JIT 编译与 MLX 图首次优化,属一次性成本
图 6 · TTFT 冷热对比(受控实验,0.6.4 / M4 Max)。条形长度按左侧刻度线性换算。这里有一个必须提醒的读法:2.5s 与 68.6s 的差距是 27 倍,但它不是「oMLX 比别的服务快 27 倍」,而是「命中与不命中的差距」。把它当成产品性能指标是常见误读。

三笔账的构成是:模型加载(9.7–11.9s,权重进统一内存)、首次推理预热(9–10s,Metal 内核 JIT + MLX 图优化)、全量 prefill(取决于 prompt 长度)。前两笔与 prompt 无关,是纯固定成本;第三笔才是缓存能优化掉的部分。

CI 与自动化场景的隐性事故

如果你的自动化流程在服务启动后立刻发请求并设置 30s 超时,会稳定撞上「30 秒无响应」,然后被误判为「服务挂了」。正确做法:启动后先发一个 1 token 的探针请求(或从管理面板 pin 模型),把加载与预热成本前置支付,再进入真实负载。

6.5 硬件档位的现实参考

机器统一内存实测模型PrefillDecode适用判断
M4 Max(40c)128 GBQwen3.8-Flash-Next oQ4e + MTP,1k→128k 上下文535 → 85548.0 → 62.5日常编码主力档,长上下文仍保 48+
M3 Ultra512 GBQwen3.5-122B-A10B-4bit768 → 94142.4 → 56.6厂商基准机,非典型用户配置
M4 Max64 GBQwen3.8-27B-4bit约 250(dense)约 42单智能体舒适的性价比档
M1 Max64 GB26B A4B / 31B dense——26B A4B 可用;31B dense 明显吃力
表 7 · 跨硬件档位的实测片段。注意 M4 Max 的 prefill 随上下文增长反而上升(1k 时 535,32k 时 846),这与直觉相反,可能源于测量口径(含预热)差异,不建议据此做容量规划。

07生态位置:Who should not use this

技术选型文档的价值,一半在「推荐什么」,另一半在「明确劝退谁」。oMLX 的适用面比它的星标数窄得多。

7.1 判断路径

① 负载是智能体后端吗?长前缀被反复重发 Claude Code / Cursor / Codex / 自建 agent loop 否 Ollama / LM Studio 为单轮聊天付冷启动税,收益为零 是 ② 需要跨平台,或依赖 GGUF 模型库吗? Linux / Windows 部署,或只能用 GGUF 权重 是 llama.cpp / Ollama 接受无跨重启持久缓存 否 ③ 模型权重能装进统一内存吗? 注意:SSD 缓存只溢 KV,不溢权重 否 软件无解 换更大内存的机器,或按小时租云 GPU 是 ④ 会频繁重启服务,或需要真并发吗? 多进程共享一台机、或一天重启多次 是 oMLX —— 收益最大 持久缓存 + 连续批处理同时吃到 否 mlx-lm 够用 oMLX 仍可用,但只多付部署复杂度
图 7 · 四问筛选。第 ④ 问是关键分水岭:仅当前三问都通过、且存在「重启」或「并发」需求时,oMLX 的独有收益(持久化 + 批处理)才会兑现。否则它在热路径上并不比 mlx-lm 更快(见 §06.3)。

7.2 应该劝退的四类用户

「我就想在本机聊天」

单轮问答、单模型、每轮 prompt 都不同——缓存命中率结构性地接近 0。你会付 20 秒冷启动、装一个 Python 环境、还要管一个后台服务,换回来的是零收益。用 Ollama 或 LM Studio。

「我要跑 70B 以上」

SSD 缓存只存 KV,不存权重。64GB 机器上 70B 4bit 大约需要 40GB+ 权重,能装但没有余量给长上下文。这类需求不是换软件能解决的,是换机器或租云。

「我需要 Linux 服务器 / 团队共享集群」

macOS 15+ 与 Apple Silicon 是硬约束(MLX 依赖 Metal,菜单栏用 PyObjC)。团队共享的正确形态是一台 Mac Studio 加反向代理,而不是「部署到 K8s」。

「我需要稳定不变的 API 契约」

近每日发版意味着端点行为、默认参数、模型兼容性都可能在两次 patch 之间变化。如果你的下游有 SLA,锁版本比追新版更重要(见 §09)。

08部署:三种安装路径与四层配置优先级

安装方式的选择不只影响便利性,它决定了你是否拥有那 30 倍的 prefill 内核。

8.1 三条安装路径的实际差异

路径命令自定义内核适用
DMG(推荐)Releases 下载 → 拖入 Applications已预编译不想碰 Xcode 的日常使用者;自带更新、菜单栏、CLI shim
Homebrewbrew tap jundot/omlx https://github.com/jundot/omlx
brew install jundot/omlx/omlx --with-custom-kernel
需完整 Xcode;可用 brew services start omlx 托管需要开机自启、崩溃自动重启的常驻服务
源码git clone … && make installmake install 含内核;make install-no-kernels 不含要改代码、跑测试、或打包自用 App
表 8 · 三条路径。最容易踩的坑:普通 pip install -e . 会静默跳过内核构建,服务仍能启动,但 GLM-5.2 一类模型会退到慢路径。

8.2 配置优先级:CLI > 环境变量 > settings.json > 每模型设置

四层覆盖关系清晰,但有一个容易误判的点:CLI 标志优先于持久化配置,且不写回。也就是说 omlx serve --max-concurrent-requests 16 只在本次运行生效,不会改变 ~/.omlx/settings.json,下次用默认值启动就回到 8。要让修改持久,必须在 /admin 面板里改。

参数默认作用与误用后果
--model-dir必需MLX 格式模型目录,支持两级组织。会同时读取 HuggingFace 缓存与 LM Studio 目录,无需重复下载。
--max-concurrent-requests8连续批处理并发上限。调高的收益是团队吞吐,不是个人延迟;超过内存带宽能支撑的量只会整体变慢。
--paged-ssd-cache-dir未启用SSD 冷层目录。必须放在内置 NVMe 上——外置盘的随机读延迟会吃掉大部分收益,而现有公开数据全部来自内置盘。
--hot-cache-max-sizeautoL2 内存热层占比。设太大挤压模型权重空间,反而触发 LRU 模型驱逐,得不偿失。
--paged-ssd-cache-max-sizeauto(空闲盘 50%)磁盘配额。auto 预算只增不减,不会因缓存增长反复收缩;有硬性磁盘约束时改固定值。
--memory-guard / --memory-guard-gbbalanced / RAM−8GB进程内存上限。低内存机器上这是防「把整机拖进 swap」的唯一保险,不要为了跑大模型关掉。
--api-key无未设置时拒绝在非 loopback 地址启动。这是内置的安全底线。
--hf-endpoint官方受限网络环境改用镜像(如 https://hf-mirror.com)。
--headless关只跑推理与管理 API,不挂 Web UI。适合纯后端部署,且不写入设置。
表 9 · 关键参数。完整列表见仓库 README 的 CLI 段,--mcp-config、--prefill-batch-size、--completion-batch-size 等按需使用。

8.3 安全模型:一条不许绕过的规则

oMLX 的安全设计有一条硬边界和若干软开关,理解这个分层很重要:

反向代理场景的注意事项

如果你用 Pinggy、Cloudflare Tunnel 之类的隧道把 localhost:8000 暴露出去,oMLX 会认为自己在 loopback 上,安全底线因此不生效。此时必须自行在 --api-key 或隧道层加认证——不要把「服务端有保护」当成「隧道后也安全」。

8.4 客户端接入

管理面板会为选定模型生成现成的配置命令,覆盖 Claude Code、OpenClaw、OpenCode、Codex、Copilot CLI、Hermes Agent、Pi、Mistral Vibe、DeepSeek Harness 等。CLI 侧可用 omlx launch list 查看本机已安装的集成。

一个实测细节值得记下:Copilot CLI 走的是 /v1/responses 端点,仓库中有注释明确指出它对 completions 端点存在问题。这类客户端差异说明「OpenAI 兼容」是个光谱而非开关——真正决定兼容性的是客户端实际调用的端点与字段,不是宣传语。

09能力边界:反证据清单

一个只列优点的技术文档没有价值。下面每一条都是「它做不到」或「它做得不够好」,且都能在实测或源码里找到依据。

边界具体表现性质缓解手段
不扩大模型上限SSD 只溢 KV,不进权重。64GB 机器仍是 64GB 的模型容量。物理约束无。换硬件或量化到更低比特。
冷启动税更重懒加载 + 内核 JIT,首请求 TTFT 约为 mlx-lm 的 2–3 倍;总预算 20–22s。设计取舍启动后打探针请求预热;管理面板 pin 模型。
平台锁死macOS 15+ / Apple Silicon only。无 Linux、无 Intel、无 Windows。硬约束无。跨平台需求请用 llama.cpp 系。
对 prompt 结构极度敏感块粒度 4096;tools 顺序变化导致级联失效;动态内容插在 system 里则永不命中。机制副作用规范 prompt 组装顺序;把动态内容后置。
无优先级调度FCFS,无抢占、无公平份额。长请求阻塞短请求(队头阻塞)。实现简化批处理任务用第二个实例或独立模型别名隔离。
版本节奏带来的稳定性风险3,465 次提交、近每日发版;曾有 72 小时内连发三个版本。项目阶段锁版本;固定内核构建;上线前跑自己的回归负载。
分布式是实验特性多 Mac pipeline 并行(Ring / Thunderbolt RDMA / JACCL)标注为 experimental。成熟度仅用于非关键扩容;生产依赖单机。
生态覆盖面仅 MLX 格式权重(GGUF 需外部 Splash 引擎);无 Gemini CLI 集成实现。生态主流模型有 mlx-community 版本;小众微调需自行转换。
基准数据自证为主官方表格来自 M3 Ultra 512GB;SSD 恢复路径在慢速外置盘上缺第三方验证。证据缺口用自己的负载实测,别外推厂商数字。
表 10 · 反证据清单。前三条属于不该尝试绕过的约束;中间四条是可以用工程手段缓解的取舍;最后两条提醒你:目前所有公开性能数字都带有环境相关性。
一个值得警惕的叙事偏差

官方宣传语是「TTFT 从 90 秒降到 5 秒以下」。这个数字在特定配置下成立,但它描述的是长上下文的重复前缀这一种场景。把它理解成「oMLX 让本地推理快 18 倍」,会导致在三类场景上做出错误决策:单轮对话、短 prompt、以及需要最大单发速度的脚本——在这三类里,oMLX 与 mlx-lm 的差距在噪声范围内,而它多出来的部署复杂度是确定的。

10反模式清单:三类会白付成本的用法

每一条都给出症状、成因、修正动作。症状部分写得具体,是为了让你能对着自己的日志认出来。

10.1 架构层

反模式症状成因修正
把 oMLX 当加速器接单轮 API命中率长期低于 5%;每次请求 8–70s;用户以为服务性能差每个请求都是新前缀,缓存层只增加查找开销与写盘 I/O单轮场景换 mlx-lm;或保留 oMLX 但显式关闭写入(X-oMLX-Cache-Store: off),只享受端点兼容
动态内容塞进 system prompt改动一行代码后命中率归零;明明「prefix 没怎么变」却总在重算时间戳、会话 ID、随机种子、当前文件内容插在 system 里 → 块 0 就变 → 链式哈希全链失效静态在前、动态在后。system 只放真正不变的部分
每轮重新排序 toolsTTFT 在 2.5s 与 80s 之间来回跳tools 渲染进前缀,顺序变化即内容变化(实测命中从 16384 归零)tools 定义做确定性排序(如按 name 字典序)并固化在代码里
让一次性任务写缓存多轮会话的命中率被「别的任务」挤掉;SSD 增长快但复用少摘要器、标题生成、意图分类各自写入大量只出现一次的前缀块这类调用加 X-oMLX-Cache-Store: off;或把它们路由到独立模型别名

10.2 调参层

反模式症状成因修正
并发数往大调单请求延迟上升、总吞吐没涨、偶发模型驱逐decode 是带宽瓶颈,8 路已把带宽利用率推到约 45%;再加只是分摊个人用保持默认 8;团队按「并发用户数 × 1.5」估,且用真实负载压测确认
hot cache 占比设太大模型频繁被驱逐又重载,出现规律性的 10s 卡顿L2 吃掉统一内存 → 触发 EnginePool 的 LRU 驱逐 → 下次请求重新加载让 hot cache 留在 auto;先保证模型权重与 KV 有空间
SSD 缓存放外置盘命中却有 1–3s 甚至更高的额外延迟;「持久化好像没用」外置 USB/网络盘的随机读延迟远高于内置 NVMe。所有公开数据均来自内置盘缓存目录固定在内置 NVMe;外置盘只放模型权重(顺序读为主)
关掉 memory guard 跑大模型整机卡死、风扇狂转、需要强制重启统一内存被 GPU 与系统共用,超限不是优雅失败而是 swap 风暴保留 --memory-guard;用 --memory-guard-gb 设定明确上限

10.3 运维层

反模式症状成因修正
客户端超时太短缓存永远建不起来;每次都是冷启动;日志里全是「连接中断」冷 prefill 需要 60s+,客户端 30s 就断开 → prefill 被打断 → 块从未写盘把首个请求的 timeout 设到 120s+;启动后先打探针预热
不 pin 模型每隔一段时间出现一次「莫名其妙很慢」TTL 到期或内存压力触发卸载,下一次请求重新付加载成本主力模型在管理面板 pin 住;其余模型设合理 TTL
跟着主干走昨晚还好今天挂;两次重启行为不一致近每日发版;0.7.3.dev2 这类 dev 版本出现在主干锁定 release tag,升级走「起旁路实例 → 跑同一负载 → 比对命中率与 TTFT」
隧道后当内网用公网可无认证调用推理接口,账单/数据双风险服务看到 loopback 就跳过验证,安全底线不生效隧道层加认证,或显式设 --api-key
用首轮 TTFT 做验收基准数字难看,误判「方案不可行」;或数字好看,上线后打脸首轮包含加载 + 内核 JIT,与被测机制无关跑三次,只取第 2、3 次;并把「重启后第一次」单列为一项指标

11落地路径:五级成熟度与验收清单

从「装上试试」到「团队共享生产」,中间隔的不是配置项,而是四个具体的验证动作。

L0 试用 装 DMG,跑通一次对话 确认端点能连 不设性能预期 L1 验证 同一请求连发三次 只取第 2、3 次 记录命中块数 重启后再测一次 L2 对齐 prompt 顺序规范化 tools 定义冻结排序 动态内容全部后置 一次性任务关写入 复查命中率 ≥60% L3 生产 pin 主力模型 启动探针预热 客户端超时 ≥120s 锁定 release tag 缓存目录内置 NVMe 验证内核已加载 内存守卫档位定档 L4 多租 按用户数算并发 批处理实例隔离 模型别名分租户 监控命中率曲线 磁盘配额告警 Profile 拆分快慢档 旁路实例做升级 每往上一级,都要回答同一个问题:命中率提高了吗?如果 L2 做完命中率仍低于 30%,说明负载本身不适合,应回退到 L0 换方案。 L1 与 L2 是纯客户端改动,零服务成本;L3 起才涉及部署与运维投入。先做完 L2 再谈扩容,是这个项目唯一正确的投资顺序。
图 8 · 五级成熟度。注意阶梯的高度不是「投入量」,而是「需要变动的范围」:L1–L2 只改调用方代码,L3 改部署形态,L4 改系统拓扑。

11.1 四个必须自己跑的验证

以下四条不依赖任何第三方基准,二十分钟内可完成,但它们能决定方案是否成立:

验证 1 · 缓存真的生效了吗

同一个请求连发三次,记录第 1、2、3 次的 TTFT。期望形态:第 1 次显著高,第 2、3 次接近且大幅下降。如果第 2 次没降,说明前缀里混进了动态内容。

验证 2 · 重启还活吗

跑一轮让缓存建立,重启服务,再发同样的请求。这是 oMLX 与 mlx-lm 唯一的分水岭。若重启后退回到冷启动水平,说明 SSD 层没生效(目录配置错误或配额为 0)。

验证 3 · 顺序敏感度

把 tools 数组顺序交换一次,再发同一请求。预期命中归零。这一步不是找 bug,是确认自己理解了机制——也顺便证明缓存判定确实在按内容工作。

验证 4 · 内核在不在

跑 native_kernel_status()。如果你用的是 GLM-5.2 / MiniMax M3 / Qwen3.5,这一项决定你是 845 还是 29 tok/s 的 prefill。静默回退不会给你任何提示。

11.2 需要长期盯的三个指标

指标健康区间告警含义
前缀命中率(命中块 / 总块)智能体负载 ≥ 60%持续低于 30% → 负载类型不匹配,或 prompt 结构有动态污染
SSD 缓存写丢弃数接近 0持续上升 → 写入队列长期打满,磁盘 IO 跟不上,考虑换内置盘或降低并发
模型重载次数 / 天≤ 1(pin 的主力模型应为 0)频繁上升 → 内存治理在赶模型,需要下调并发或 hot cache 占比
表 11 · 三个比 tok/s 更有诊断价值的指标。stats.py 已内置这些计数器,接监控前先人工看一周。

12结论:一次成功的「错位移植」

oMLX 做的事情,用一句话概括是:把一个为数据中心设计的服务模型,硬塞进了一台个人电脑,而且塞对了。

vLLM 的 PagedAttention 诞生于「显存稀缺、请求高并发、GPU 昂贵」的云环境,目的是让同一块 GPU 服务尽可能多的用户。把它搬到 Apple Silicon 上,前提全变了:没有独立显存(统一内存),没有多用户(通常一个人),GPU 不贵(已经买断了)。按常理,这套工程应该水土不服。

但它找到了一个在新环境里成立的对应物:云上稀缺的是显存,Mac 上稀缺的是「不被打断的连续计算时间」。智能体的存在制造了这个稀缺——每一轮都要求机器把两万个 token 重新算一遍,而这台机器同时还要跑 IDE、浏览器和 Docker。

于是那些在云上用来省显存的机制,在这里被用来省时间:分页是为了碎片化复用,这里是为了让不同会话共享同一段前缀;写时复制是为了并发安全,这里是为了让多个客户端读同一份缓存而不互相污染;LRU 驱逐是为了容量管理,这里是为了在 64GB 里同时放下模型、KV 和 IDE。连 SSD 落盘这个在云端几乎不存在的设计(云盘太慢),在 Mac 上反而成立——因为本地 NVMe 的随机读延迟,比重新跑一次 16K prefill 快了两个数量级。

判断(这是观点,不是共识)

oMLX 的技术价值不在于它比谁快,而在于它证明了「本地推理服务的缓存可以是持久的」这件事值得做。一旦这个前提被接受,后续所有优化(KV 量化、跨进程共享、多机分片)都有了新的落点。目前它更接近「一个方向正确的早期实现」,而不是「一个可以无脑上生产的组件」——但这个方向本身,比它当下的完成度更重要。

12.1 三句话行动建议

  1. 如果你的日常是 Claude Code / Cursor / Codex 指向本地模型,现在就换,收益立刻可见,而且改造成本只有一次配置。做完 §11.1 的四个验证再决定是否长期使用。
  2. 如果你只是在本机聊天或跑脚本,不要换。你付出的部署复杂度与冷启动税是确定的,换回的收益是零。
  3. 无论哪种情况,先花半天把自己的 prompt 组装顺序规范化。这一步的收益不依赖 oMLX——它对任何带前缀缓存的推理服务都成立,而它恰好是绝大多数团队从未做过的事。

12.2 尚待观察的三个问题

13参考来源

按证据类型分组。项目自述与第三方实测严格分列,厂商基准单列,便于读者按需回溯。

一手项目资料

第三方受控实验(证据强度最高)

厂商自测基准

背景资料

阅读提示

本报告所有性能数字均保留原始来源的单位与量级,未做跨来源归一化。不同来源的硬件、版本、模型、prompt 长度、是否含预热均不相同,横向相减或相除会得到错误结论。表格内的对比只在同一来源内部有效,跨来源的并列仅用于展示量级。