本地与混合推理
并非每个提示都需要位于他人数据中心里的前沿模型。Bromure Agentic Coding 可以借助 Apple 的 MLX 框架,直接在你 Mac 的 GPU 上运行开放权重编码模型——无需云端往返、无需按令牌计费,也不会有任何代码离开这台机器。一个工作区可以完全在设备上运行,可以在使用云端的同时保留设备端安全网,也可以逐个代理地混合使用两者。
本地推理是每个工作区各自的选择,与 Fusion 相互独立:Fusion 决定有多少个模型回答一个提示,而路由决定由哪个后端来回答它。本章解释推理在何处运行、模型目录与下载如何工作、三种路由模式与混合策略引擎、如何将单个代理指向本地模型,以及如何观察引擎的行为。该窗格逐字段的设置参考位于 本地模型设置。
注意: 这里的所有内容都运行在 Mac 宿主机上,而不是在虚拟机内部。Virtualization.framework 不会给 Linux 客户机提供任何 GPU、Metal 或 MLX 访问权限,因此设备端推理必须在 macOS 上进行,并由客户机跨 传输边界 访问。需要 Apple Silicon(M1 或更新款)——与应用本身的要求相同。
推理在何处运行
引擎已内置于应用中;无需安装任何东西,无需 Python 环境,也无需像 Ollama、LM Studio 或自托管 vLLM 实例这样的外部服务器来指向。当某个会话需要设备端推理时,应用会启动一个私有的 MLX 引擎,并将客户机代理接入它。
引擎子进程
MLX 引擎作为应用自身二进制文件的一个受监管子进程(bromure-cli model _mlx-engine)运行,而不是在应用内部运行。这是刻意的隔离:加载一个对 Mac 内存来说过大的模型只会杀死引擎子进程,而绝不会影响应用或其正在运行的虚拟机。父进程会自动重启崩溃的引擎,最多三次;之后它会放弃,并输出日志行 engine child crashed repeatedly — giving up (a model likely OOMs this Mac)。应用退出时子进程会被杀死,而由硬崩溃留下的任何孤儿进程都会在下次启动时被回收。
一个引擎服务于所有打开的工作区。它在 127.0.0.1 上绑定一个由内核分配的回环端口——绝不会是 0.0.0.0,也绝不会是惯用的 11434(该端口被特意留空,好让单独安装的 Ollama 或 LM Studio 继续工作)。它在一个内存预算下并行加载多个模型,该预算为宿主机统一内存减去 16 GB(下限为 8 GB),在首次使用时惰性加载每个模型,并在预算紧张时驱逐最近最少使用的那个。打开或关闭工作区会通过一个管理端点实时重新配置正在运行的引擎——无需重启。
在引擎预热期间,会话窗口会显示一个状态胶囊,写着 正在启动本地引擎…,它会在引擎回应就绪探测后消失。
客户机如何访问引擎
VM 内的代理从不直接与引擎通信。它们指向合成宿主机 https://bromure.llm——一个没有真实 DNS 的名称——而宿主机侧的 MITM 代理服务器会拦截它,施加它对云端流量所施加的相同 提示注入扫描 与 追踪捕获,然后将请求转发给宿主机上的引擎。因此本地推理绝不是盲区:它跨越相同的传输边界,并落入与调用 Anthropic 相同的审计记录中。
代理被固定到一个哨兵模型名 bromure-local,宿主机会将其重新映射到工作区当前激活的模型。切换激活模型是一次宿主机侧的重新映射,无需重启代理——代理会继续请求 bromure-local,只是背后换成了另一个模型。
注意: 若某个模型的架构尚不被 MLX 引擎支持,则会以一个清晰、永久的错误失败——"its architecture … isn't supported by the on-device engine yet — pick a different model"——作为客户端错误返回,因此代理不会循环重试它。
模型目录
目录是一份经过精选的预转换 MLX 模型清单,每个模型都针对代理编码中最重要的一点进行过审核:量化模型经常破坏工具调用,因此每个目录条目都带有一个工具调用已验证徽章。条目还记录了下载大小和最低统一内存要求,窗格会将其转化为一个 RAM 适配门槛。
应用内置了一份基线目录,因此窗格在第一天就能离线工作。启动时应用会从 https://dl.bromure.io/mlx/catalog.json 获取一份刷新后的目录;较新的清单会完全替换基线(而不是合并)。从已发布目录中移除的模型会从列表中消失——除非你已经安装了它,在这种情况下它会作为已安装的额外项保留下来。
基线中附带的模型
捆绑目录是一组跨越各内存档位的 Qwen3 编码模型。这四个模型都经过工具调用验证:
| 模型 | 磁盘占用 | 最低统一内存 | 推荐 |
|---|---|---|---|
| Qwen3 8B (4-bit DWQ MLX) | 4.3 GB | 16 GB | 是 |
| Qwen3-Coder 30B-A3B (4-bit DWQ MLX) | 17 GB | 32 GB | 是 |
| Qwen3-Coder-Next 80B-A3B (mxfp4 MLX) | 42 GB | 96 GB | 是 |
| Qwen3-Coder 480B-A35B (4-bit MLX) | 270 GB | 512 GB | 否 |
80 亿参数的模型可在任何受支持的 Mac 上运行;4800 亿参数的专家混合模型需要一台 512 GB 的机器(M3 Ultra),因此未被标记为推荐。刷新后的目录可能会在无需更新应用的情况下加入更新或更大的构建版本。
徽章与 RAM 适配门槛
窗格中的每一行(以及 bromure-cli model catalog 的每一行)都带有两个徽章:
- 一个大小档位——S 表示所需内存为 16 GB 或更少的模型,M 至多 32 GB,L 至多 64 GB,XL 超过此值。
- 一个针对你的 Mac 统一内存的适配判定:适配、紧张或无法适配。"适配"要求模型的最低内存加上为操作系统及其他所有内容预留的 16 GB 余量;"紧张"意味着它能加载,但几乎没有富余空间;"无法适配"意味着模型的最低内存超过了你的内存。无法适配的行会显示为灰色,无法被选择或下载。
例如,Qwen3 8B 模型(最低 16 GB)在 16 GB 的 Mac 上显示为紧张,在 32 GB 或更高时显示为适配;Qwen3-Coder 30B-A3B(最低 32 GB)从 48 GB 起显示为适配。
使用目录之外的模型
目录是一份精选菜单,而不是一道栅栏。任何已经是 MLX 格式的 Hugging Face 仓库都可以通过其 org/repo 名称从命令行拉取(参见 命令行参考);这样的模型被视为未经测试——它不带工具调用保证,也不带 RAM 适配保证。GGUF 格式的仓库会被直接拒绝,因为 GGUF 是 Ollama 和 llama.cpp 的路径,而非 MLX:
That's a GGUF (Ollama/llama.cpp) model — Bromure serves MLX weights only.
下载与存储模型
权重由一个内置的纯 Swift 下载器直接从 huggingface.co 拉取——无需 Python,无需 mlx_lm.convert,也没有任何形式的转换。下载器从 Hugging Face API 读取仓库的文件列表,将每个权重文件流式写入一个 .partial 临时文件,然后原子性地将其重命名就位;仓库中的文档、图片和任何 GGUF 文件都会被跳过。在开始之前,一次磁盘空间预检会尽早失败,而不是把你的磁盘塞满:
Not enough disk space: 40 GB free, but about 270 GB is needed.
模型存放位置
下载的权重会落入你的 Application Support 目录下一个扁平的、按仓库划分的布局中:
~/Library/Application Support/BromureAC/models/<org>--<name>/
每个目录保存着模型的 config.json、其 .safetensors 分片以及其分词器。下载是一种全局副作用,在每个工作区之间共享——拉取一次模型就会让所有工作区都能使用它。此前由其他工具缓存在 ~/.cache/huggingface/hub 中的模型会通过硬链接一次性迁移到该目录中,因此不会重复下载任何内容。
窗格中的下载状态
每个模型行上的操作控件反映其状态:
| 状态 | 控件 | 含义 |
|---|---|---|
| 未安装 | 下载按钮 | 可供拉取(若模型无法适配则禁用)。 |
| 下载中 | 进度条 + 字节标签 + 停止(✕) | 一个由磁盘上真实字节数驱动的确定型进度条;✕ 取消并删除该部分文件。 |
| 已中断 | 已中断 + 恢复 / 丢弃(垃圾桶) | 一次因应用崩溃或被杀死而未完成的拉取。恢复会从停止处继续;丢弃则删除该部分文件。 |
| 失败 | 重试按钮 | 下载出错;悬停可查看原因。 |
| 已安装 | 已安装 及一个 移除 菜单项 | 已完整下载并准备好提供服务。 |
由于下载器可在文件粒度上恢复,被中断的拉取绝不必从头开始,而部分下载也绝不会被误认为一次有效的安装——一个进行中的哨兵文件会一直标记它,直到最后一个字节落地。
提示: 你下载的第一个模型会被自动设为工作区的激活模型,因此一个全新的工作区只需一次点击就能从"没有本地模型"变为"准备好提供服务"。
为工作区启用本地模型
在工作区浏览器中打开该工作区,点击编辑工作区,并选择本地模型窗格(薄荷绿的 CPU 图标)。窗格起初只是一个开关;模式选择器和模型列表只有在本地推理开启后才会出现。
- 打开启用本地模型("在这台 Mac 上运行编码模型,而非使用云端。")。这会将工作区的路由从云端切走;将其重新关闭则恢复为云端。
- 选择一个模式:
- 本地——始终在设备上运行 会将每个请求保留在这台 Mac 上。正如窗格所指出的,回复是私密的,但速度更慢,并受限于你能装入内存的模型。
- 混合——云端,回退到本地 会照常将请求发送到云端,仅在云端不可达时才回退到设备端模型——云端的速度与质量,加上一张本地安全网。
- 在模型列表中——标题为 模型 · N GB 统一内存,其中 N 是你 Mac 的内存——下载一个模型,然后用其左侧的单选圆点将它选为激活模型。灰色的行所需内存超过了你 Mac 所拥有的内存。
- 点击存储。
路由模式与激活模型选择会在你存储时保留;而下载由于是全局的,无论你是否存储都会立即发生。如果你所选的多个模型加起来需要接近或超过你 Mac 的内存——引擎会并行为它们提供服务,因此它们的内存会累加——窗格会显示一条警告,并要求你去掉一个或选择更小的模型。
完整的字段列表与默认值见 本地模型设置。
路由:云端、本地与混合
路由是每个工作区顶层的选择,决定由哪个后端为代理的 LLM 流量提供服务。它有三个取值:
| 模式 | 行为 |
|---|---|
| 云端 | 默认值。请求会透传到真实的提供商(可选地伴随宿主机侧的凭据替换;参见 凭据)。 |
| 本地 | 每个 LLM 请求都在设备上提供服务。 |
| 混合 | 默认使用云端,并按策略驱动回退到本地模型。 |
选择本地或混合会自动启用 MITM 拦截路径——你从不需要为它单独拨动一个开关。每个已回答的轮次都会在 追踪 中被打上一个 服务方标记,记录由哪个后端回答:cloud 或 local-<model>。你可以在追踪检查器中读取它,逐轮确认混合会话实际去往了何处。
路由是在窗格中设置的每个工作区默认值,但也可以在一台正在运行的虚拟机上通过命令行用 bromure-cli vm routing 更改(参见 命令行参考)。
注意: 本地路由会将代理指向 Anthropic 和 OpenAI 宿主机(以及
bromure.llm哨兵)的流量重新路由到引擎。它不会劫持一个本身已固定到真实云端凭据的代理——例如,共享同一工作区的订阅版 Claude 代理会保留其真实的云端流量。若要强制某个特定代理无视工作区路由而走本地,请使用它自己的本地模型认证模式,见下文。
混合回退策略
混合不是单一开关,而是一个小型策略引擎,经过调优以绝不会在中途切换模型而打断一条编码轨迹。每个决策都在会话边界处做出,此后即粘性——一旦一段对话被路由到某个后端,它在该会话余下的时间里就会一直留在那里(一致性守卫)。
什么会触发回退到本地
对于一个新会话,第一个匹配的规则获胜,顺序如下:一个已固定的粘性会话、一个已耗尽的云端令牌预算、一个不健康的云端(健康门槛)、一次拆分比例分配,否则走云端。在一个已经发往云端且在途中的请求期间,有两种情况会强制立即在本地模型上重放,并将该会话在其整个生命周期内固定为本地:
- 硬错误——一次被拒绝或超时的连接,或来自提供商的 HTTP
429、529或5xx。 - 一次错过的软截止——在 TTFT 预算(默认 5 秒)内没有出现首个令牌。
其底层是一个保守的健康门槛:当最近约十个请求中至少出现三次失败时,或当 TTFT 的指数加权移动平均值攀升超过 8 秒时,云端会被标记为不健康,且只有在三次干净、快速的探测之后才会恢复。当云端不健康时,新会话会直接走本地,而无需各自先付出软超时的代价。两个后端从不相互竞速——没有推测性对冲,也没有双重花费;回退只有在一次真实的触发之后才会启动。
可调旋钮
有三个旋钮被暴露出来,且只能通过命令行针对一台正在运行的虚拟机使用(参见 命令行参考)。它们按工作区持久保存,但除非路由为混合,否则会被忽略:
| 旋钮 | 命令 | 默认值 | 效果 |
|---|---|---|---|
| 云端令牌预算 | bromure-cli vm hybrid budget <tokens> <vm> | 0(无限制) | 每个滚动 24 小时窗口内云端所服务令牌数的上限;一旦超出,新会话会走本地,直到窗口滑回上限之下。 |
| 软 TTFT 超时 | bromure-cli vm hybrid ttft <seconds> <vm> | 5 | 请求被取消并在本地重放前,没有首个令牌的秒数。 |
| 本地拆分 | bromure-cli vm hybrid split <0-100> <vm> | 0 | 即便云端健康,也主动固定为本地的新会话百分比,以在成本、延迟和隐私之间取得平衡。 |
健康门槛的内部机制(8 秒 EWMA 阈值、失败窗口以及恢复探测次数)是固定的,不可由用户调节。
将本地模型作为代理后端
路由是一个覆盖整个工作区的维度,但你也可以将单个代理指向本地引擎,而让工作区的其余部分保留在云端。在工作区的 代理 标签页中,每个代理——Claude Code、Codex 或 Grok——都有一个认证模式选择器,其选项包括本地模型。选择它,挑一个已安装的模型,那么该代理就会完全针对设备端引擎运行,并使用引擎会忽略的虚拟云端密钥;MITM 会对其施加与对云端流量相同的保护。
这使得混合工作区成为可能——例如,一个使用订阅的 Claude Code 与一个使用本地模型的 Codex 并存。工作区级别的本地路由从不劫持云端代理的真实流量,而每个代理的本地模式也从不泄露给其他代理。当你在本地模型窗格中打开或关闭本地推理时,应用会自动让每个代理的认证模式保持同步。
Fusion 中的本地模型
本地模型也是 Fusion(多模型综合功能)中的有效参与者,并且它可以在其中扮演两种角色之一:
- 作为一条腿。 在要融合的模型下勾选本地模型并挑一个已安装的模型;它的草稿答案会与 Claude、Codex 和 Grok 一同加入面板。由于它运行在你的 Mac 上,它是一条零边际成本的能干额外之腿。
- 作为评判器。 选择本地作为 Fusion 评判器提供方,可将分析与综合阶段完全在设备上运行,让整个评判步骤都不离开云端。
这两种角色都至少需要一个已下载的本地模型;在此之前,Fusion 窗格中相应的行会显示为灰色,并附有提示,让你先在此处下载一个。完整的面板工作流程参见 Fusion。
工具调用修复
量化模型在代理使用中最主要的失败模式,是把一个工具调用输出为纯文本,而不是代理可以执行的结构化调用。一个修复代理服务器位于引擎前面——客户机和 MITM 本地路由都指向它,而非指向裸引擎——并透明地挽救这些情况。
对于每个响应,它都会缓冲输出,并重新解析模型可能泄露一个调用的许多临时形态,包括:
<function name="write_file" arguments='{…}'>
<tool_call>{…}</tool_call>
[{"name": "…", "parameters": {…}}]
以及 Qwen3-Coder 的原生 <function=Name><parameter=k>v</parameter></function> 格式和 Gemma 的通道格式。它会从所发现的任何内容中合成正确的工具使用块,并以代理原生传输形态、协议正确的 SSE 重新发出该消息。它还会检测卡住的前导语——模型叙述一个动作("Now I'll create the file:"),然后结束其轮次却始终没有调用工具——并至多重新提示两次以恢复缺失的调用。每当声明了工具时,都会在系统提示后追加一条工具格式提醒。
对于本地推理,修复始终开启,且无需配置。引擎问题会被转换为传输原生的错误体,因此代理会显示真实原因,而不是一个通用的失败,例如:
Local inference engine unreachable (starting up, reloading a model, or stopped) — retry in a moment.
监控引擎
窗口菜单下的两个窗口可以让你在不打开 Console.app 的情况下观察设备端推理。
推理指标
窗口 → 推理指标… 会打开一个实时遥测面板(窗口标题为 推理指标),标题为标签 本地推理 及引擎的回环地址。它把引擎的 Prometheus 指标解析为卡片——解码 tok/s、预填充 tok/s、运行中、等待中、处理中、平均延迟、缓存命中、Metal 内存、生成令牌 和 提示令牌——外加一个 已加载模型 列表和一个包含原始表格的 所有指标 展开项。
该窗口每 5 秒轮询一次,且仅在它打开时轮询。解码和预填充速率在设计上是终身累计比率,因此它们读起来是稳定的平均值,而非跳动的每秒增量。当引擎宕机时面板会显示 engine not reachable,而在一次长时间生成期间它可能会短暂显示 engine busy (timed out)。
推理引擎日志
窗口 → 推理引擎日志… 会打开引擎子进程输出加上父进程生命周期事件的实时跟踪(窗口标题为 推理引擎日志)——模型加载、"serving"、每个请求的统计信息,以及任何 OOM、崩溃、重启或加载错误。各行采用颜色编码(红色表示失败,绿色表示健康事件,橙色表示警告,蓝色表示进行中的工作)。工具栏提供一个 筛选… 字段、一个 自动滚动 复选框、复制 和 清除;当尚未发生任何事情时它显示 No inference-engine activity yet。缓冲区是一个上限为 5,000 行的内存环形缓冲区,而每一行也会被镜像到应用的 stderr,因此从终端启动 bromure-cli 会给你一份持久副本。
命令行参考
模型与路由命令会与正在运行的应用的控制 API 通信,因此应用(或其代理)必须正在运行。作用于特定虚拟机的命令带有一个末尾的 <vm> 参数,它是一个 VM id 或一个工作区名称。持久的每工作区默认值在上文描述的 GUI 窗格中编辑;这些命令作用于一个实时会话。周边的命令集参见 自动化与 CLI。
模型管理:
bromure-cli model catalog [--all] [--offline]
bromure-cli model pull <catalog-id | org/repo>
bromure-cli model ls
bromure-cli model use <catalog-id | org/repo> <vm>
bromure-cli model rm <catalog-id | org/repo>
| 命令 | 作用 |
|---|---|
model catalog | 列出精选模型,带有适配与工具调用徽章及一个已安装的勾选标记,并打印你 Mac 的统一内存。它会先刷新实时目录(若失败也不致命)。--all 会包含无法适配这台 Mac 的模型;--offline 会跳过刷新,仅使用捆绑的和缓存的目录。 |
model pull | 按目录 id 或任意 Hugging Face MLX 仓库下载一个模型。它会验证仓库是 MLX(拒绝 GGUF),预检磁盘空间,然后显示一个由磁盘上真实字节数驱动的进度条。 |
model ls | 列出已安装的模型及其磁盘占用和仓库。 |
model use | 为一台正在运行的虚拟机的工作区设置激活的本地模型——一次对 bromure-local 哨兵的宿主机侧重新映射,无需重启代理。 |
model rm | 从磁盘移除一个已安装模型的权重。 |
一台正在运行的虚拟机的路由与混合策略:
bromure-cli vm routing cloud|local|hybrid <vm>
bromure-cli vm hybrid budget <tokens> <vm>
bromure-cli vm hybrid ttft <seconds> <vm>
bromure-cli vm hybrid split <0-100> <vm>
bromure-cli vm fusion enable|disable <vm>
vm routing 设置后端模式;三个 vm hybrid 旋钮调节回退策略(且仅在路由为混合时才起作用)。vm fusion 会启用相互独立的多模型面板,文档见 Fusion。
内部诊断命令
这些隐藏子命令是为开发与故障排除而存在的,不属于日常工作流程。引擎子进程本身由应用作为 bromure-cli model _mlx-engine --config <path> 生成。
| 命令 | 用途 |
|---|---|
bromure-cli model _mlx-serve <repo> | 为一个模型启动进程内 MLX 服务器并阻塞,打印其端口和密钥以供 curl 测试。 |
bromure-cli model _mlx-selftest <repo> | 加载一个模型,生成一次,并打印 TTFT 和解码 tok/s。 |
bromure-cli model _repair-serve --engine-port <port> | 独立运行工具调用修复代理服务器,针对一个正在运行的引擎。 |
bromure-cli model _tc-test <path> | 在一个已保存的文本文件上运行工具调用挽救,以检查泄露调用的提取。 |
bromure-cli model _spec-bench <main-repo> --draft <draft-repo> | 对一对模型基准测试推测解码关闭与开启的对比。 |
性能预期
设备端推理以速度换取隐私与成本,而这种权衡是真实存在的——请相应地设定预期:
- 吞吐量随模型和芯片而变化。 像 Qwen3 8B 这样的小模型在任何受支持的 Mac 上都很敏捷;大型的专家混合构建则更慢,且需要多得多的内存。推理指标 窗口会显示你的硬件上实际的解码和预填充速率——这是最诚实、可据以规划的数字。
- 第一个请求要为一次模型加载买单。 一个冷引擎在预热时会显示 正在启动本地引擎…;大型权重需要真实的时间才能加载进内存,然后首个令牌才会出现。后续请求会跳过这一步。
- 多轮代理循环在第一轮之后会更便宜。 引擎会保留一个每对话的前缀 KV 缓存(每个模型至多四个会话槽),因此每个代理轮次只预填充新的令牌,而非整个记录——而一条侧链不会驱逐主对话的缓存。
- 生成是串行化的。 引擎一次运行一个生成,因此多个繁忙的会话是共享 GPU,而非真正并行运行。
- 推理模型会静默思考。 对于发出
<think>块的模型,该块总是在答案抵达代理之前被剥除——你获得质量而没有记录的臃肿。
提示: 如果一个本地模型感觉慢或卡住,请先打开 推理引擎日志 窗口:模型加载、驱逐、OOM 重启以及不支持的架构错误都会以纯文本形式浮现在那里,这比从代理一侧猜测要快。