Magnitude 源码深读:它不是又一个 Ollama,而是一台本地 Agent 推理控制器

从源码、实测与论文出发,拆解 Magnitude 如何完成硬件探测、模型选型、投机解码、驻留管理与 Agent 接入。

我点开 Magnitude 仓库前,以为会看到一套“替你装好 llama.cpp”的脚本。真正顺着调用链读下去,先撞见的却是内存域、校准证据、置信度、租约、回收原因和协议网关。它真正想自动化的不是模型启动,而是本地 Agent 推理里的资源决策。

本文基于 Magnitude 主干源码 99d236a(2026-09-06)阅读。为了不写成 README 的中文扩写,我另外做了三件事:跑了一条原仓 Rust 单测,复算了模型排序曲线,并直接统计了该提交里的模型目录。

先给结论:

Magnitude 之所以长成“目录 + 规划器 + 驻留管理器 + 协议网关”,是因为本地推理没有一个长期有效的静态配置:模型、上下文、量化、内存占用、硬件后端和 Agent harness 任意一项变化,原来的最优解都可能失效。

如果这个约束不存在,最朴素的 llama-server -m model.gguf 就够了。它真正先崩的地方不是“模型不能跑”,而是模型勉强装下后,长上下文、别的应用抢内存、工具调用格式或 harness 协议中的任意一个变量把体验拖垮。

名词速查

术语一句话解释
GGUFllama.cpp 常用的模型文件格式,把权重、量化与模型元数据装在一起
harness包在模型外面的 Agent 运行框架,例如 Codex、Claude Code、OpenCode,负责工具、上下文和循环
ICNInference Control Node,Magnitude 中负责模型、硬件和推理生命周期的 Rust 服务
ACNAgent Control Node,面向 Agent 会话并把推理能力投影给上层客户端的服务
内存域一组共享同一块物理内存的计算设备;Apple Silicon 的 CPU 与 GPU 就属于同一统一内存域
KV cache模型保存历史 token 注意力中间结果的缓存,长度越长,占用越大
投机解码便宜的草稿器先猜多个 token,目标模型一次并行验证,接受后批量前进
驻留租约活跃请求对内存中模型实例的占用凭证;租约未释放时不能随便卸载模型

先拿到地图:四个活动部件

把仓库里的数万行代码压缩成一张地图,只需要盯住四个部件:

部件保存或决定什么拨动它会怎样坏掉会怎样
硬件与容量物理内存域、后端、保留内存、模型与 KV 开销上下文或并发升高,能装下的模型变少把共享内存重复计算,或者把桌面系统挤到失去响应
目录与排序模型能力、量化保真度、本机预计速度偏好从 Fast 移向 Smart,排序会跨过明确阈值推荐“强但慢到不能当 Agent”的模型
推理与驻留模型何时加载、谁正在使用、何时卸载首次请求变慢,热请求变快;内存压力下主动回收常驻吃光内存,或请求中途被误杀
harness 网关Codex、Claude Code、OpenAI、Anthropic 等协议与配置换 harness 不必重做模型侧适配API 看似兼容,工具调用、流式事件或恢复配置却坏掉
flowchart TD
  A[Codex / Claude Code / OpenCode] --> B[可逆连接器]
  B --> C[ACN 协议网关]
  C --> D[ICN 推理控制节点]

  subgraph Plan[启动前的规划]
    E[硬件快照与校准]
    F[策展模型目录]
    G[容量与速度估算]
    H[模型排序]
    E --> G
    F --> G
    G --> H
  end

  D --> E
  H --> D

  subgraph Run[请求到来后的执行]
    I[模型驻留状态机]
    J[Rust 推理引擎]
    K[固定版本 llama.cpp]
    L[本地 GGUF 权重]
    I --> J --> K --> L
  end

  D --> I
  J --> M[进度、计时与结果]
  M --> C

这个分层很像操作系统:目录是“可运行程序清单”,规划器是 admission control,驻留状态机像进程与内存管理,协议网关则像系统调用兼容层。llama.cpp 是执行内核,却不是完整产品。

第一层:先回答“装不装得下”,而且不能只看 free memory

最容易写错的本地模型选择器,是拿“当前空闲内存”减模型文件大小。Magnitude 没这么做。

1. 先把设备合并成物理内存域

hardware_snapshot_from_devices() 会先判断平台是不是 macOS ARM64。Apple Silicon 的 CPU、Metal GPU 与系统共享一块物理内存,因此只能记一次;独立显卡则按物理身份分组。

这里有个很老派、也很正确的工程判断:显示名称和容量相同,不足以证明两个后端看到的是同一张卡。源码只在后端报告了相同物理身份时合并视图;没有 ID 的设备仍然保持后端隔离。否则 CUDA、Vulkan 等视图一重叠,容量账会凭空多出一张 GPU。

2. 把“长期能用”与“此刻空闲”分开

每个内存域同时保存:

  • total_capacity_bytes:物理总量;
  • stable_capacity_bytes:扣除系统保留后的稳定预算;
  • current_free_bytes:此刻还空闲多少。

稳定预算用于推荐,瞬时空闲量用于真正加载前的再次准入。这个两阶段设计很关键:Chrome 此刻多开一个页面,不该让模型推荐列表抖来抖去;但真正加载时,又不能无视 Chrome 已经吃掉的内存。

system_memory_thresholds() 还设了两道线:规划时至少保留 max(总内存 10%, 2 GiB),运行时跌到 max(总内存 5%, 1 GiB) 的危险区就应该中止推理。按源码公式手算:

物理内存推荐阶段保留运行中止保留
16 GiB2 GiB1 GiB
32 GiB3.2 GiB1.6 GiB
64 GiB6.4 GiB3.2 GiB

这不是“尽可能把模型塞满”的策略,而是给操作系统留出生存空间。

3. 容量账不止模型权重

capacity_summary_from_accounting() 对每个内存域分别累计四笔开销:模型权重、上下文、计算工作区、辅助模型或投影器。最终不是只回一个 fits,还会指出最大缺口和限制资源。

这和本站此前算过的《KV Cache 的显存账》是同一条约束:模型文件能装下,不等于带着 50K 上下文与并发槽位还能装下。

第二层:速度不是型号表,而是“这台机器搬多少字节”

Magnitude 的速度估算最值得读的函数是近 400 行的 estimate_generation_performance()。把校验分支拿掉,主干其实很清楚:

  1. 从模型得到每个 decode 步骤真正会读取的张量;
  2. 对 MoE 只计算每 token 激活的专家权重,而不是总参数;
  3. 按上下文长度累计 KV cache 读取;
  4. 用当前后端、设备和张量类型的校准带宽换算时间;
  5. 把权重时间与 KV 时间相加,再取倒数得到 tok/s;
  6. 根据测量波动、回退校准、跨内存域放置等情况给出上下界和置信度。

这就是 2009 年 Roofline 模型 在本地 LLM 上的一个具体后代。Roofline 的核心问题是:一段程序到底受峰值计算量限制,还是受内存带宽限制?小 batch 自回归 decode 通常落在带宽侧,于是最粗但很有用的近似就是:

每秒 token 数 ≈ 每秒能搬的字节数 ÷ 每生成一个 token 要读的字节数。

这也是《Decode 为什么是带宽受限的》里那条式子的产品化版本。Magnitude 比纸面公式多做了一步:它不把带宽当厂商标称值,而是把校准结果连同稳定性和相对离散程度一起带进估算。

但要守住边界:源码里仍有稠密、MoE、循环模型、稀疏注意力、跨域放置等经验效率系数。这是一台带置信度的估算器,不是真实工作负载 benchmark。置信度变低是诚实信号,不是精度凭空消失。

先补一口数学:为什么排序要用乘法和对数

下面会出现指数和对数,只需要两个直觉。

第一,0.9^0.5 是把 0.9 的惩罚“开一半”。指数越接近 0,这个维度越不重要;指数越大,它对结果压得越重。

第二,对数会压缩后半段差距。速度从 10 提到 20 tok/s,体感通常很明显;从 80 提到 90 tok/s 仍有价值,却不该压倒模型能力。对数曲线正适合表达这种边际收益递减。

第三层:模型推荐不是排行榜,而是一条可解释的效用曲线

Magnitude 先在 modelRankingScores() 里提取三个归一化分数:

  • intelligence:目录中策展的智能分;
  • speed:本机预计生成速度;
  • fidelity:当前量化版本的保真等级。

速度统一在最多 50K 已占用上下文下比较。短上下文模型取自己的上限,缺少这个上下文档位的性能估算就不参与评分。这不是随手挑的展示数字,而是在定义产品的工作负载:Agent 会话不是空提示词跑分,长历史下仍能生成才算可用。

速度分在 40 tok/s 前线性增长,之后转为对数,到 100 tok/s 封顶。源码曲线复算如下:

预计速度归一化速度分
10 tok/s0.130460
20 tok/s0.260921
40 tok/s0.521841
60 tok/s0.733430
80 tok/s0.883554
100 tok/s1.000000

随后 localModelRankingUtility() 用一条乘法效用函数排序:

U=I0.9p×S0.9(1p)×F0.1U = I^{0.9p} \times S^{0.9(1-p)} \times F^{0.1}

p 就是 Fast → Smart 滑块,0 偏速度,1 偏智能。保真度始终占 0.1 的指数,不会因为用户偏快就完全消失。

我构造了两个小模型做复算:Smart 模型智能 0.90、20 tok/s、保真 0.90;Fast 模型智能 0.72、60 tok/s、保真 0.95。结果是:

Fast → Smart 的 pSmart 效用Fast 效用胜者
0.00.2953130.752654Fast
0.50.5155400.746420Fast
1.00.9000000.740238Smart

解出交点是 p ≈ 0.827212。也就是说,这个例子里滑块要非常靠近 Smart,较聪明但慢三倍的模型才会翻盘。

这个设计有两个好处。乘法会惩罚明显短板:一个维度接近 0,别的高分不能轻易把它遮住;对数速度分则避免“已经很快”的模型仅凭多十几个 tok/s 吞掉能力优势。

它也有一个不能藏起来的软肋:速度是本机估算,智能与量化保真却来自目录策展。在本次提交的 models.json 里,智能分带着外部榜单来源与日期。换句话说,Magnitude 自动化了硬件侧的测量,却仍要相信维护者对“好模型、好量化”的判断。目录质量就是系统质量的一部分。

第四层:投机解码不是一个开关,而是一份可装载计划

标准投机解码的祖先是 Leviathan、Kalman、Matias 的 Fast Inference from Transformers via Speculative Decoding 与 Chen 等人的 Accelerating Large Language Model Decoding with Speculative Sampling:便宜模型先起草,目标模型并行验收,用校正采样保持目标分布不变。

如果只停在论文伪代码,产品里会少掉三本账:草稿模型本身也占内存;草稿与目标必须兼容;验收率太低时,草稿成本会超过省下的串行 decode。

Magnitude 把这些问题前移到了计划阶段。preflight_with_backend() 会区分嵌入目标模型的草稿头和独立草稿模型,检查 MTP、DFlash、DSpark 各自参数,再用底层后端算出实际可用的 n_minn_max。不兼容时先拒绝计划,而不是等第一条用户请求把服务打崩。

执行阶段的 verify_speculative_batch() 保存采样器检查点,批量验收草稿;底层要求 replay 时恢复采样器状态再走一遍。这里能看到论文里的“分布不变”如何变成工程约束:它不是一行 accepted += n,还要维护 token 历史、位置、采样器状态、终止条件和统计。

本次提交的目录统计是:22 个模型、59 个量化变体,其中 8 个模型声明了投机解码配置——4 个 DFlash、3 个 DSpark、1 个嵌入式 MTP。目录不是“支持 speculative decoding”这句布尔宣传,而是把目标、草稿来源和方法绑定到具体模型。

论文谱系也正好解释这三种方法为什么共存:

DFlash 论文报告过超过 6 倍的无损加速,DSpark 报告过相对 MTP-1 生产基线 60%–85% 的单用户速度提升;这些是论文各自环境里的结果,不是 Magnitude 在任意 Mac 或 PC 上的承诺。本地硬件、batch、模型配对和验收率变了,收益也会变。这笔“验收率 × 验证成本”的第二本账,可接着看《投机解码的第二本账》

第五层:模型不是常驻进程,而是有租约的缓存

模型选好了,也不等于应该永远占着内存。

ModelResidency::run() 是一个事件循环:有命令就处理命令,没有命令且存在空闲截止时间,就等计时器。请求获得模型时会拿到租约;租约全部释放后才重新开始空闲倒计时。idle_expired() 同时确认“没有租约”和“截止时间已到”,才以 IdleTimeout 原因释放模型。

系统内存压力是另一条更硬的路径。监控器发现余量进入危险区,会阻止新的内存准入并请求回收推理 worker。两条路径不能混成一个“最近没用就杀掉”:

  • 空闲回收优化资源利用率,可以温和等待;
  • 压力回收保护整台电脑,需要主动打断并留恢复窗口。

这正是操作系统缓存与进程生命周期的共同祖先:磁盘上的 GGUF 是可重建资产,内存中的模型实例是昂贵缓存,活跃租约则是不能回收的引用。

第六层:接入 Agent,难点不只是一个 OpenAI Base URL

只看表面,给 Codex 配一个本地 base_url 就结束了。源码展示的现实更麻烦。

makeCodexConnector() 会读取 Codex 自带模型目录,生成 Magnitude 模型目录,写入 provider 与模型选择,并保存连接前的模型和 provider。断开时,它只移除自己拥有的配置;如果用户连接后又选择了官方模型,还会保留用户的新选择。

这是一项很容易被忽略的技巧:集成不只要可安装,还要可逆,并且不能把用户在集成期间做的新决定一起回滚。

服务侧也没有假装所有客户端真的说同一种 OpenAI 方言。makeInferenceProxy() 明确区分 openaianthropiccodexclaude-code;Codex 的 Responses 与 WebSocket、Claude Code 的 Anthropic 语义各有自己的 gateway。

这和《什么是好的 Agent Harness》的结论呼应:模型 API 只是神经接口,真正决定 Agent 能否长期工作的是外层对工具、历史、错误、流式事件与恢复行为的约定。

我从这个项目里带走的六个技巧

1. 稳定事实负责推荐,瞬时事实负责准入

不要用会抖动的 free memory 生成长期推荐,也不要拿昨天的稳定预算替今天的真实加载做担保。同一资源保留两种时间尺度,是比“找一个最准数字”更好的设计。

2. 估算结果要携带证据质量

Magnitude 不只回 tok/s,还带上下界和置信度。跨内存域、校准回退、测量不稳定都会降级信心。数据一旦脱离来源与置信度,就很容易被 UI 包装成虚假的精确值。

3. 边际收益要在产品函数里写出来

40 tok/s 后使用对数,不是数学炫技,而是在代码里明确表达“更快仍然好,但越来越不值钱”。很多排序器的问题并非权重不准,而是默认假设收益永远线性。

4. 目录应该是可执行规格,不是链接收藏夹

模型、量化、上下文、智能分、许可证、目标权重 commit、草稿模型和投机方法被绑成一个可解析条目。这样“推荐这个模型”才是一份能落地的计划。

5. 把不兼容尽量前移到 preflight

投机解码最怕“参数都合法,但组合起来不能启动”。先让规划器验证目标、草稿、上下文和后端,比请求到来后才发现失败便宜得多。

6. 集成代码必须设计 disconnect

只会写配置的连接器是安装脚本;知道自己改了什么、能恢复什么、哪些用户新选择不能碰,才是生命周期管理器。

Magnitude、Ollama、LM Studio、直接 llama.cpp 怎么选

这里不做“谁更强”的总榜,只按控制面需求分流:

你的主要需求更直接的起点理由
精确控制 GGUF、后端参数和最新推理特性llama.cpp它就是底层执行引擎,暴露的控制最多
快速拉模型、跑通本地 API,并连接常见应用Ollama官方提供模型管理、OpenAI/Anthropic 兼容接口和 Agent 启动入口
需要图形化模型管理,同时保留 headless 服务LM Studio / llmsterGUI 与无界面 daemon 都是正式产品路径,也支持按需加载
希望 Agent 根据本机给出策展模型方案,并自动维护 harness 配置与运行生命周期Magnitude它把选型、规划、协议适配和驻留管理放在同一控制面

它们正在互相吸收能力,所以边界会移动。真正稳定的判断问题是:你要的是执行引擎、通用本地模型服务、桌面工作台,还是面向 Agent 的决策控制面?

亲手验证:一次“绿色但零测试”的小翻车

我第一次运行容量测试时用了:

cargo test --manifest-path inference/Cargo.toml \
  -p icn-hardware stable_capacity_ignores_volatile_free_memory \
  -- --exact

命令是绿色的,但输出写着 running 0 tests。原因是 Rust 测试的完整名字带模块前缀,--exact 把短过滤词排除了。去掉 --exact 后才得到真正的结果:

running 1 test
test tests::stable_capacity_ignores_volatile_free_memory ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 46 filtered out

这个测试构造了总容量 1000、当前只剩 1、系统保留 100、模型运行需要 400 的场景。结果仍判定“稳定容量 900,模型可规划”,正好证明推荐阶段刻意忽略瞬时 free memory;真正加载前会由另一道准入检查兜底。

这次验证没有下载真实模型,也没有测实际 tok/s。本文关于速度的判断只覆盖估算公式与源码路径,不能替代目标机器上的端到端 benchmark;关于隐私与离线行为,也只核对了代码与官方设计,没有做网络抓包审计。

小结

Magnitude 的根本约束不是“本地模型难启动”,而是本地 Agent 的最优推理配置会随硬件、上下文、模型与 harness 一起变化。最朴素做法先崩在资源账和兼容账,而不是一条 API 请求。

它给出的可复用答案是:稳定容量做推荐、实时余量做准入;Roofline 式字节账估速度;乘法效用函数做多目标排序;preflight 守住组合兼容;租约与状态机管理内存驻留;可逆连接器守住用户配置。

想在 10 分钟内判断这项目是不是你的菜,不必先下载几十 GB 权重:克隆仓库后打开 packages/acn/src/local-model-ranking-policy.ts,改两组智能分与 tok/s,复算一次滑块交点。如果你开始争论“40 tok/s 后为什么该变成对数”,你已经碰到了 Magnitude 真正在做的事——把本地推理的隐含偏好写成可审查的代码。

参考来源

工程实现与官方文档

论文