信息截止:2026 年 11 月 8 日 05:40:09(北京时间)。 本文核对官方发行记录、PR、文档和固定源码,未安装该构建,也未在 CPU 或 GPU 上复测。下文的示例数据来自贡献者的开发期记录;解释、取舍和验证建议属于本站分析。

llama.cpp 的 b12191 增加了一条很适合用来理解本地 MoE 推理的路径:专家权重放在主机内存,设备缓存只保存其中一部分;当请求用到不在缓存里的专家时,可以让 CPU 直接计算一部分,而不是全部先上传到设备。

这项改动值得独立阅读,因为它把一个常被忽略的选择交给部署者:处理一个小批量请求时,搬运权重的时间,可能比就地算完更长。 但 CPU 分担也会增加计算、结果传输和同步开销,改变缓存的后续内容。最佳设置由整个执行路径决定,不能只看哪块芯片的峰值算力更高。

这次新发布了什么

GitHub Release 的 published_at 为 2026 年 11 月 7 日 21:11:11 UTC,即北京时间 11 月 8 日 05:11:11,在本期此前 24 小时窗口内。GitHub 将 b12191 标记为 pre-release;对应提交是 e95426f16982054c379ca7bdc8186996dd22f0fe,本文固定在这个版本,不把后续主分支变化混进来。发行记录

底层 PR #30966 于 10 月 22 日创建,11 月 7 日 16:20:59 UTC 合并。因此,新鲜度依据是功能进入这次公开构建,而非声称 CPU 与 GPU 协同计算的思路今天才出现。公开 PR

已有的 --moe-cache-mib 控制设备上的专家缓存容量。新增 --moe-cache-cpu-share F 控制一部分缓存未命中专家由 CPU 计算;两者默认分别为 0 MiB 和 0,缓存与 CPU 分担都不会仅因升级就自动打开。另有 scripts/moe-cache-tune.py,用少量校准运行拟合时间模型,帮助选择参数。固定版本文档

专家缓存与 KV 缓存,节省的是不同工作

MoE 的路由会为输入选择一部分专家参与计算。只激活部分参数能减少每步计算,但不意味着未激活的权重永远不需要保存;后续 token 仍可能选择其他专家。

当显存不能容纳全部专家时,一种配置是让专家权重留在主机内存,把经常使用的部分缓存到设备。这里缓存的是模型权重;KV cache 保存的是随请求上下文增长的注意力状态。两者都占设备内存,但不是同一种缓存,增加一方的预算可能挤占另一方及运行时缓冲区。

b12191 的路径将选中的专家分成三类:

  1. 缓存命中:权重已在设备,继续由设备计算。
  2. 未命中且分给 CPU:使用主机中的权重计算,不为这次使用把该专家装入设备缓存。
  3. 其余未命中:上传权重,更新缓存,再由设备计算。

因此,F=0.5 不是“让 CPU 承担一半模型”或“一半 token”。它针对符合条件的每层、每次微批处理中去重后的缓存未命中专家数,不是未命中访问次数;还有离散计数、缓存状态和执行路径的限制。源码使用累积余数维持长期分担比例;并非每一步都恰好按同一小数比例分配。缓存分配实现

缓存命中率降低,仍可能更快

CPU 分担主要在主机读取与计算足够快、权重上传较慢时有利。官方文档举出的条件包括较快的 RAM、PCIe 4 链路或未使用页锁定内存的专家权重;它们是值得测试的条件组合,不是硬件代际对应的速度保证。

加载方式也会改变结论。固定版本文档说明,-lm none、-lm mlock 和 -lm dio 下主机专家通常会使用页锁定内存,--no-host 是例外;默认 mmap 路径不属于这种情况。页锁定内存能让设备更充分地利用上传链路,CPU 分担这时往往无益。比较前后速度时,不能同时换加载方式,却把全部变化归功于 CPU 占比。加载与传输条件

PR 中展示的一组 Qwen3.8-Flash-Next IQ4_NL 开发期校准记录,很直观地说明了取舍:

缓存大小 CPU share 记录的生成速度 命中率
6,464 MiB 0 24.2 token/s 73.3%
6,464 MiB 0.95 47.6 token/s 61.5%
19,392 MiB 0 65.5 token/s 93.5%
19,392 MiB 0.95 54.1 token/s 71.6%

小缓存时,较低的命中率与更快的生成同时出现;大缓存时,同样把 share 提到 0.95 却更慢。原因不能简化成“命中率无用”:命中率没有计入省下的上传,以及 CPU 路径新增的工作。 当 CPU 处理的未命中不进入缓存时,后续缓存内容也会变化。

这只是 PR 作者展示的开发期单组输出,不是本站复现,也不是 b12191 的独立端到端基准。页面示例未在相邻说明中完整给出 CPU、GPU、RAM 与互联配置,不能据此给读者承诺相同速度或推广出一个通用加速倍数。原始输出与上下文

两个细节决定它能否在实际负载上成立

先填满缓存,再分担。 若从空缓存开始就把绝大多数未命中交给 CPU,设备缓存会很慢才热起来。当前实现等相应缓存组的槽位全部装入专家后,才启用 CPU share;此前未命中仍上传。测量时应分别看冷启动与热缓存,不能拿热态预测解释首轮对话延迟。

优先把较少复用的未命中留给 CPU。 当前代码按结合近期使用与频次的 CRF 分数选择,较低分的未命中优先留在 CPU;持续被使用的专家会累积分数,获得进入设备缓存的机会。这与随机分配不同,缓存容量和请求内容都会影响哪些未命中专家由 CPU 计算,以及后续缓存状态。缓存策略及分担条件

还有实际兼容门槛。这个版本的缓存路径主要面向解码、推测解码等小批量:源码在一次微批次的 token 数超过 32,或所需专家数超出槽位容纳条件时,不走该缓存映射。CPU 分担的计算图还排除启用 LoRA、在专家 FFN 前施加路由权重,以及旋转专家权重的相应路径;后端对该批量的卸载判断也会影响 CPU 路径是否可用。能加载某模型,不等于这项优化一定生效。缓存与后端检查、计算图条件

自动调参表里,哪些是测出来的

调参脚本的价值,是减少盲目枚举,但它的输出需要分层阅读。

第一层是校准实测。 脚本在两个缓存大小、share 为 0、0.5 和 0.95 的组合下启动服务,共六个配置。固定源码中,首次配置生成两段各最多 2,048 token,其余配置各生成两段最多 512 token,受上下文容量约束。它使用同一个故事续写提示,改变随机种子,记录生成耗时及专家访问轨迹;这不是多种业务请求的代表性测试集。

第二层是拟合与回放。 脚本拟合每 token 时间中的上传、CPU 计算、额外拆分开销、重叠以及其余工作,再把记录的专家路由送入缓存策略模拟器。其他容量与 share 网格中的速度是预测值。它跳过合并轨迹前一半 token 作为预热,输出明确标为热缓存预测;不能把整张表当成每个配置都跑过。

第三层才是推荐。 在候选缓存大小对应的 share 中,脚本倾向选择接近最快预测的较低 share。源码具体判断的是每 token 耗时不超过最小值的 1.05 倍,当候选容量不能容纳全部专家时,再输出最大候选缓存大小的选择;若全部专家可放进设备,脚本会建议移除 -cmoe,或缩小候选缓存范围后再调。它没有优化 CPU 能耗、多用户尾延迟或所有可能的缓存容量。校准、时间模型与选择代码

拟合失败或校准相对误差超过 10% 时,脚本会报错而不输出最终推荐。这个检查能发现部分噪声和不合适的拟合,却不能证明预测会适用于新提示词、长会话、多并发,或机器同时运行其他任务的情况。

使用时还有两处容易忽略:

  • --max-mib 限制的是候选推荐容量,固定源码中的两个校准容量另按专家大小和可用设备内存决定,不能把它当成六次校准运行都遵守的显存硬上限。
  • 旧校准结果按命令字符串复用。即使命令不变,二进制、同路径模型文件或机器状态仍可能已变;需要重测时,用新的 --out 目录保存结果,避免把旧校准误当当前机器测量。

以上两点来自该固定版本的脚本行为,并非声称后续版本仍然如此。tune 与 run_server 实现

怎样把它变成一次可靠的本地实验

最合适的起点,是已经需要把专家放在主机内存、主要服务小批量生成的部署。如果全部专家本来就能放进设备,或者主要瓶颈是长提示词处理,这条路径未必是优先项。

建议把验证拆成三步:

  1. 固定比较条件。 锁定源码、模型文件与量化、线程数、上下文、加载方式、专家放置及其他进程负载。保留相同缓存大小、share=0 的对照,避免混入扩大缓存的收益。
  2. 验证推荐,而非直接采用。 用实际对话、代码和长输出负载重新测量推荐值及相邻值;分别记录冷态和热态生成、首 token 延迟、CPU 占用、内存与显存。若服务多人,还要看并发和尾延迟。
  3. 保留输出质量回归。 设计意图是让原来被选中的专家在不同设备执行,并非通过少算专家来省事;但不同后端与数值路径仍需检查。不能仅凭参数说明宣称逐 token 一致或质量完全不变。

b12191 提供的最有用能力,是把“未命中后一定上传”变成一个可以测量、比较的选择。它让有限显存下的 MoE 推理多了一种调配方式,也提醒我们:缓存、传输与计算必须一起计时,单独最大化命中率或 CPU 参与度,都不等于最大化用户真正得到的速度。

公开来源

本文仅供信息参考,详情请见免责声明。

← 继续阅读更多内容