首页 / 文章 / qMLX:通过极致优化Mac Studio最大化我的AI幻觉
← 返回
AI技术

qMLX:通过极致优化Mac Studio最大化我的AI幻觉

✍️ zhirenhun 📅 2026/7/12 👁 22 阅读 ⏱ 20 分钟
qMLX:通过极致优化Mac Studio最大化我的AI幻觉

qMLX:通过极致优化我的Mac Studio来最大化AI“精神病”体验

2026年7月10日·12分钟阅读

原文:https://mrzk.io/posts/qmlx-maximising-ai-psychosis-minmaxing-mac-studio/


目录

  • 为何更换模型
  • 真正的工作:修复三个Bug
  • Bug一:系统提示中的时间戳
  • Bug二:从未发生的回复
  • Bug三:检查点存储中的“毒药”
  • 最终结果
  • 真实数据
  • qMLX与相关工具
  • 结论

在一个5万token的对话中提出后续问题后,第一个token出现需要三到五分钟。不是完整回答,仅仅是第一个token。这根本不是聊天机器人,而是批处理作业——你只能趁它“思考”时去泡杯咖啡。

对于其他十几位痴迷于极致优化Mac Studio的极客们,我带来了好消息。所谓好消息,就是我花了三周时间调试一个缓存泄漏问题,这样你们就不必了。

这一切始于antirez/ds4,一个在消费级硬件上运行大模型的出色实验。两个月前,我冲动购买了M3 Mac Studio Ultra。我担心RAM价格会让96GB配置很快变得遥不可及(剧透:确实如此),而且我真的很想在育儿假期间深入探索本地推理栈。目标很简单:在一台机器上运行前沿模型,保持其“热启动”,并拥有一个真正理解上下文的对话式AI。

结果比想象中复杂。模型确实能在统一内存中运行并给出连贯回答。但经过数周的日常使用后,我做出了一个重大转变:放弃了DS4 Flash,转而使用Qwen 3.5 122B。随后发生了两件独立的事情,值得分开来看。模型更换是速度和适配性的决策。而让新模型真正可用,意味着要修复我自己服务栈中的三个Bug——这些Bug与运行哪个模型毫无关系。


为何更换模型

DS4 Flash确实是一个优秀的模型,antirez的栈也是出色的作品。但它不适合我的工作场景。这是一个适配性问题,而非模型本身的问题。

我的使用场景是长上下文代理式编程:结对编程时,模型需要处理数千token的对话、代码和工具输出,而我需要近乎即时的响应以保持工作流。对于这种特定工作流,预填充延迟是致命问题。超过5万token后,一个简单的后续问题需要三到五分钟才能出现第一个token,你无法与一个让你等泡杯茶的模型进行结对编程。等它反应过来,我已经转向下一个问题了。这并非DS4在绝对意义上慢,而是该设置在我的硬件上处理长上下文预填充的方式与我的工作流需求不匹配。

Qwen 3.5 122B在我关心的每个维度上看起来都更适合M3 Ultra:

  • 接近前沿,完全本地化。无需API调用,无速率限制,数据不离开机器。一个能与专有系统媲美的122B模型,完全离线运行。
  • 适合Ultra带宽的活跃参数规模。M3 Ultra的内存带宽很大。一个拥有约100亿活跃参数的122B MoE模型正好处于甜蜜点,GPU可以持续向计算单元输送数据而不会停滞。
  • 工具调用与推理能力的良好平衡。具备足够的推理深度处理复杂代码逻辑,同时足够稳健以支持可靠的函数调用和代理编排。
  • 适合统一KV缓存的合适规模。在96GB统一内存下,低比特宽度的122B模型恰好为深度SSD支持的KV缓存留出足够空间——这正是实现长上下文保留而无需交换的关键。

我能找到的最接近的服务栈是rapid-mlx。但它在混合注意力机制上走了不同方向。鉴于这种分歧,我选择fork它。结果就是qMLX,可在GitHub上获取。这个fork专门针对混合注意力进行了优化(文末有更多说明)。


真正的工作:修复三个Bug

模型能运行,但开箱即用时却因另一套与模型本身无关的原因而无法使用:每次后续消息都会从头重新处理整个对话。在13万token的上下文中,这意味着模型在说出第一个词之前需要等待数分钟。

原因在于混合注意力架构,它混合了GatedDeltaNet(SSM)层和密集注意力层。SSM层中的循环状态无法回退或裁剪到更早的位置,因此为避免内存泄漏,内存中的缓存会丢弃任何包含这些层的条目。对于这个模型,内存中的前缀缓存每次都会miss。在一个正常窗口中,我测量到内存命中为零次,而磁盘命中为109次。唯一保持模型“热启动”的是磁盘缓存:将注意力KV检查点保存到SSD,在下一轮对话中恢复。磁盘恢复在这里不是后备方案,而是整个缓存的全部。而且它不断以三种不同的方式崩溃,每个问题都隐藏在前一个之后。


Bug一:系统提示中的时间戳

KV重用是字节精确的。如果提示哪怕发生微小变化,匹配会在第一个差异处失败,之后的所有内容都会重新计算。

代理框架在每一轮对话中都会将一个唯一的消息ID写入系统提示。这个唯一值位于13万token提示的靠前位置,意味着提示永远不会字节稳定。第二轮对话在开头几百个token内就与第一轮不同。缓存的代理被丢弃,整个系统提示重建,匹配在早期就出现偏差。每一轮都是冷启动。

修复方法是删除这一行。消息ID是装饰性的,没有代码会读取它,而且代理已经通过每轮的用户消息获得了ID。任何每轮唯一的元素都不应放在缓存的前缀中。应将其放在提示中本该变化的部分。


Bug二:从未发生的回复

系统提示修复后,缓存保持了一段时间,然后在对话深入时再次崩溃。

当你在模型仍在回复时发送消息,代理会中断运行。这是正确行为。但在中断路径上,它跳出循环却没有保存已经流式传输的回复。推理服务器已经将这些token解码到其KV缓存中,但历史记录中缺少了助手轮次。对话主体深处出现偏差,导致冷填充。

我通过数据库证明了这一点:我的四条消息连续存储,中间没有助手轮次,而一条明显已流式传输到屏幕的回复却从历史记录中消失了。它从未被写入。修复方法是在中断路径上、在跳出循环之前持久化流式传输的回复——这与代码已为断开的网络流所做的恢复处理相同。额外好处是,代理不再忘记自己说过的话。

通用规则:如果一个生成过程的token能够到达服务器的缓存,那么该生成过程必须在所有退出路径上(包括混乱的路径)提交到历史记录。


Bug三:检查点存储中的“毒药”

前两个修复之后,缓存在一轮又一轮对话中保持热启动,但恰好在使用工具或被中断的轮次中会完全冷启动一次,然后恢复。

两个写入者同时访问检查点存储。一个写入真实内容:一个以提示为键的检查点,供下一轮恢复使用。另一个是后台钩子,每生成256个token就写入一个完整检查点,但没有附加token键,因此永远无法被匹配或恢复。这是无效数据。而且它会计入磁盘容量限制。

一个长时间使用工具的轮次会生成大量token,从而触发大量这种垃圾写入,导致存储超过容量限制,而驱逐策略会优先丢弃最旧的数据,结果把唯一有用的检查点连同垃圾一起丢弃。当时磁盘上:一个目录中有27GB无法匹配的数据体,挤占了真正重要的检查点。修复方法让驱逐策略优先丢弃无法匹配的检查点,并在恢复功能开启时完全禁用垃圾写入器。有用的检查点得以保留,下一轮对话能恢复它,冷填充就此停止。


最终结果

同一个对话,以前每轮需要冷填充3万token,修复后,从3.1万token增长到5.7万token:

uid=58 HIT cached=53267 prefill=670
uid=59 HIT cached=54009 prefill=33
uid=60 HIT cached=54113 prefill=1671
uid=61 HIT cached=55867 prefill=45
uid=62 HIT cached=55996 prefill=1869

每一轮都会恢复先前的上下文,仅对新消息进行prefill。从过去的数分钟缩短到亚秒级。检查点目录保持整洁状态,每个检查点都可匹配,没有垃圾数据。

缓存不仅起到帮助作用,它改变了问题的形态。以下是相同重复提示在缓存开启和关闭时的对比,prefill时间以秒为单位,数值越低越好:

关闭缓存时,重复的32k token提示每次仍需要88秒的prefill时间。开启缓存后,仅需0.64秒。绿色条形图确实存在,只是紧贴底部。这个差距正是磁盘恢复子系统的全部意义所在,并且随着上下文增长而扩大:1k时快13倍,32k时快137倍。

本文中的所有数字均来自同一台机器:配备M3 Ultra的Mac Studio,28核CPU(20个性能核心和8个能效核心),60核GPU,96GB统一内存,运行macOS 26.4。这是qMLX唯一经过测试的配置。我尚未在降频版Ultra、没有Ultra带宽的M系列或其他内存配置上运行过,因此请将这里的阈值(guard大小、解码曲线形态)视为该特定机器的数据,直到有人在其他环境中复现。

以下是原始硬件在不同上下文长度下的表现,prefill吞吐量和decode吞吐量分别测量:

Prefill在短上下文时峰值约为700 tok/s,随着attention KV增长,到64k时降至386。Decode从55 tok/s下滑至28。这条decode曲线是25%的密集注意力层带来的可见成本:每个生成的token都会重新读取整个KV缓存,且读取量随上下文增长。而75%的DeltaNet层携带恒定大小的循环状态,完全不会变慢,这正是为什么在上下文增加64倍的情况下,性能仅温和下降2倍,而非断崖式下跌。混合设计在这里并非妥协,而是让长上下文decode保持可用的关键。

有一个粗糙的边缘问题,因为这是alpha版本,我宁愿指出它而不是隐藏它。在安装恢复的缓存之前,有一个内存余量guard,用于估算反量化后的KV是否能适配可用内存。它目前过于保守:高估了临时反量化占用空间,并在物理上限之下留下了过宽的余量,因此会拒绝实际可以容纳的恢复操作。我目前默认关闭了它,需要彻底重写以诚实估算大小。安全本能是正确的,确实无法容纳的恢复应该被拒绝,但当前的数字在"狼来了"。


诚实的数字

还有一件事,因为这是一个小小的骄傲点。在构建这些指标时,我拒绝了常见的吞吐量谎言:总token数除以墙钟时间,这种方法将几乎瞬间完成的prefill折叠进数字中,使慢速解码器看起来很快。

Prefill和decode是不同的阶段,我分别测量它们。Decode tokens per second是仅在解码窗口内生成的token数。Prefill吞吐量排除了从缓存提供的任何内容,因为缓存没有计算这些token,而是跳过了它们。我坚持整个表面的规则是:任何在恒定解码速度下发送更长提示时上升的数字,都是在欺骗你。

在这个模型上真正重要的缓存命中率是磁盘恢复命中率,因为内存中的缓存结构上已经死亡。这就是仪表盘前端显示的那个指标。

这引出了我拒绝在没有警告标签的情况下打印的数字。以下是基准测试会放在幻灯片上的"吞吐量"——处理的token加上生成的token除以墙钟时间——与你实际感受到的解码速率对比:

红色条形图显示350到600 tok/s。绿色条形图——决定回复实际流式传输速度的那个——是28到55。红色数字并非错误,而是毫无意义:在恒定解码速度下发送更长提示时它会立即上升,因为它被几乎瞬间完成的prefill token主导。发送46k token的提示,生成128个token,你可以宣传500 tok/s,而用户看到文本以38的速度出现。

在对话中真正重要的数字,两者都不是孤立的。因为缓存使prefill在热轮次中几乎免费,持续的对话吞吐量就是解码速率:短上下文时大约55 tok/s,即使在64k时也能保持在28左右。正是缓存让我能够引用诚实的数字,同时听起来仍然很快。


qMLX和工具

以上所有内容都已发布。qMLX是我对rapid-mlx的分支,专门针对Apple Silicon上的混合Qwen 3.5和3.6模型进行了优化,并包含了本文讨论的磁盘KV恢复子系统。没有raullenchai/rapid-mlx,这一切都不会存在。基础引擎、OpenAI和Anthropic API接口以及MLX服务路径都是他们的。qMLX仅添加了混合感知的磁盘恢复、驱逐和指标工作,以及Qwen专门化。感谢你们提供的构建基础。

在确定qMLX之前,我进行了一个非常简短、非常严肃的命名轮次,产生了三个我很高兴被淘汰的候选名称:

  • QweMLX(发音为"Q-MLX"):听起来像拼写错误。
  • Qwengine(发音为"Q-engine"):听起来像水管供应。
  • q-ml:太接近现有的mlx-lm,且难以搜索。

胜出者qMLX至少可发音且足够独特以便grep搜索。

  • qMLX (marzukia/qMLX):分支版本。磁盘KV检查点和恢复、bug三中的可匹配感知驱逐、混合缓存感知记账以及诚实的指标界面。
  • 基准测试工具(repo中的bench_qmlx.py):上述图表背后的pp/tg扫描。它使用唯一提示访问运行中的服务器,使缓存无法伪造prefill数字,诚实地将prefill和decode分阶段,并具有比较模式,可以冷启动和热启动运行每个上下文大小,精确展示缓存的作用。可以在你自己的机器上重新运行。

如果你在Mac上运行混合MoE并正在与冷prefill作斗争,上述三个bug是我首先会检查的地方。


结论

这一切都不是对DS4 Flash的否定。该堆栈是一项出色的工程,它证明了可以在消费级硬件上运行大型模型。只是它不适合我的特定工作流程。

Qwen 3.5 122B才是适合的那个。在解决了三个棘手的bug之后,它足够快速和稳定,可以用于日常的长上下文结对编程,而这正是我一直在追求的。这就是alpha版本。

顺便说一句,图表是用charted制作的,这是我的零依赖Python图表库。


作者

Andryo Marzuki

我是Andryo——兼职气候风险负责人,全职过度思考者。我白天将晦涩的法规和地理空间数据转化为勉强可用的平台,晚上则在思考能否将自己自动化出工作岗位。我不小心构建了几个获得奖项提名的工具,这听起来比当时的感觉更令人印象深刻。这个博客是我记录最佳拖延行为、半生不熟的见解,以及在野心与危险短注意力之间挣扎的地方。


←→ junkyard.sh:从付费墙的恐怖中拯救有用的应用 2026年7月1日

预填充速度与上下文长度关系图 解码速度与上下文长度关系图 缓存命中率对比图 吞吐量对比图

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

Qwen Mac Studio 本地模型 LLM优化 MLX 缓存修复
← 上一篇
Mesh LLM:基于iroh的分布式AI计算
下一篇 →
Jetson Nano:Ollama与最优量化

📌 相关推荐

Mesh LLM:基于iroh的分布式AI计算
2026/7/12
结构化输出 vs 工具使用 vs 预填充:2026年从Claude获取JSON
2026/7/11
在AWS Bedrock上构建生产级AI代理——值得关注的架构与代码决策
2026/7/11
← 返回文章列表