开源llama.cpp Releases·原文 2026年9月11日

llama.cpp b10909:Metal后端融合表重构,修复约5% token生成性能回退

这次更新把Metal后端所有可融合算子模式集中到一张融合表,同时修掉一个因输出索引用错相对索引、导致norm/MUL融合静默失效并造成约 5% token生成回退的bug。

AI解读:这条更新主要面向在苹果芯片(Metal)上跑llama.cpp的开发者,核心是把Metal后端的融合逻辑从散落各处改成单一融合表,并修掉一个性能bug。

bug的后果很具体:图优化阶段传了相对索引而不是绝对图节点索引,导致几乎全部融合的最后一个节点没被识别为输出,norm/MUL融合被静默关闭,token生成速度回退约 5%。

对普通用户来说,如果你用Metal后端跑本地模型,这个版本可能让生成速度回到应有水平;但本次代码没有给出端到端基准数据,实际提升幅度取决于具体模型与设备。

本次还新增了gated_delta_net与KV cache拷贝的融合、一套通用融合统计接口,以及按后端记录融合次数的回归测试基线(已提交 110 个dummy架构、298 行),任何融合模式悄悄失配都会在CI中被计数比对拦住。

llama.cpp发布b10909版本,主要改动集中在Metal后端:所有可融合算子模式被集中声明到一张融合表(ggml-metal-fuse.cpp),由图优化器和算子编码器共用。

修复了一个输出索引bug:ggml_can_fuse_subgraph_ext期望outputs数组存放绝对图节点索引,但融合表查询传入了相对索引(n_ops - 1),导致每个模式的最后一个节点不被识别为输出,并触发可省略的引用计数检查,几乎使所有融合失败。

该bug静默禁用了norm/MUL融合,造成约 5% 的token生成性能回退;修复方式是传入最后一个节点的绝对图索引。

新增GGML_METAL_FUSE_GDN_CACHE融合:当gated_delta_net算子后跟一个把递归状态快照散射进KV cache的cpy时,内核直接把快照写入cache缓冲区,尾随cpy被省略。

新增通用融合统计能力:通过ad-hoc ggml_backend_reg_get_proc_address机制暴露ggml_backend_fusion_stats_init、_reset、_get_stats、_set_enabled,标签由ggml_metal_fuse_label从融合表合成(例如 "GATED_DELTA_NET+CPY")。

新增test-fusion回归测试:对test-llama-archs生成的每个dummy模型分别以融合开启/关闭模式运行,报告各融合计数器以及融合与未融合logits之间的NMSE,并与已提交的按后端基线(MTL.csv,覆盖 110 个dummy架构、298 行)对比。

融合表如何被两个阶段共用

Metal后端所有可融合算子模式现在只在ggml-metal-fuse.cpp中声明一次,由图优化器(ggml_metal_fuse_max、packing)和算子编码器(ggml_metal_fuse_next、compute)共同使用。

两个阶段共用同一张模式表,并加上ggml_can_fuse_subgraph_ext做结构检查,区别只在模式检查所用模式:优化阶段用STRUCTURAL(此时张量尚未分配),计算阶段用FULL(包含Metal缓冲区放置信息)。

这一改动还保护snake激活(MUL + SIN + SQR + MUL + ADD)在图优化期间不被重排,此前它没有这层保护。

gated_delta_net与cache拷贝的融合细节

gdn的输出还有其他消费者(attn scores视图),因此它不像省略链模式那样是简单链式结构。融合模式上的一个 'raw' 标志会跳过通用链/形状检查与ggml_can_fuse_subgraph_ext检查,让模式专属检查回调成为唯一校验者。

该标志后来更名为 'unsafe',以明确表示这个模式让融合模式退出通用省略链安全网,其检查回调必须自行重建安全保证。

  • packing(ggml_metal_fuse_max)现在匹配与计算阶段相同的视图透明节点序列,因此gdn + cache cpy组会与中间视图一起打包,并在重排后保持相邻。
  • 融合后的cpy是gdn的视图消费者(直接写cache),其mem-range在编码器中被跳过;跳过仅限通过视图消费前一融合节点的CPY节点,不影响其他融合。
  • 原先从上游草稿带过来的is_view_consumer mem-range跳过被移除:保留被省略cpy的mem-range只会在融合点增加一个保守内存屏障,不会移除屏障,最坏情况是每次gdn+cache-cpy融合多一个虚假屏障,在Qwen3.5-0.8B Q8_0上处于运行间噪声范围内。

融合统计与回归测试

测试工具现在可以统计每种融合模式触发的次数并切换融合开关。上下文挂载在Metal设备(而非最后一个后端上下文)上,因此计数器跨上下文累计,读取始终一致。

test-fusion在单一设备上以单线程编码(n_cb == 0)运行,对每种模式(prefill / decode)报告各融合计数器,以及融合与未融合logits之间的NMSE,还有对CPU参考的NMSE。

融合模式静默停止匹配(或不该触发时触发)会被计数与已提交基线的比对捕获为回归;--record写入黄金基线,--check(默认)校验基线。未融合运行作为对照,其计数器必须全为零。

NMSE在结果为NaN或该架构在设备上本就损坏时(例如Metal上的plamo2)会被跳过,因此计数检查是硬性门槛。基线计数只依赖图结构,不依赖权重(已跨权重种子验证稳定)。

  • 提交的基线为MTL.csv,覆盖 110 个dummy架构(298 行),使用逗号分隔与定宽对齐列。
  • prefill与decode每图融合计数相同,因此基线为每个标签只存一行,mode = "any",大小减半。
  • 融合统计API通过ad-hoc get_proc_address机制以通用名称解析;不导出该接口的后端会让测试报错失败。
  • --model FILE可对单个模型文件运行融合回归测试,与 --models DIR互斥;另加了 --help/-h。
  • CLI中 --backend改名为 --device,backend_name改为device_name;仅在对ggml后端接口(ad-hoc proc-address机制)时才保留 "backend" 一词。
  • 基线输出文件名改用后端基础名(例如通过ggml_backend_reg_name得到MTL),因为计数依赖后端而非具体设备索引。

CI与相关修复

融合测试需要Metal且会生成大量dummy模型,因此从通用ctest套件移到ci/run.sh中的gg_run_test_fusion,并像gg_run_test_llama_archs_tensor_split一样以GG_BUILD_METAL为门槛;它先用test-llama-archs -o生成dummy模型,再对照已提交基线校验融合计数。test-fusion.cpp仍会被构建(llama_build),但不再注册为ctest。

dummy模型由多个测试复用,因此模型生成被移到独立步骤,不再放在test_fusion内部。融合测试后来被移到专用workflow,并且只在ggml变更时运行。

  • test-llama-archs在OpenVINO平台无法构建,因此该平台跳过dummy模型生成。
  • qwen3tts dummy模型此前使用 4096 的词表而codec head为 3072,导致图用 -inf填充输出、使test-fusion的NMSE变成NaN;现已改用精确的codec head大小,不再产生填充。
  • plamo2图被修复,其融合模式拆分随之改变(RMS_NORM+MUL从 11 变为 10,RMS_NORM+MUL+ADD从 3 变为 4,总数不变),基线已重新生成。
  • test-fusion中原本的 "skip" 逻辑被移除;"multi-output stuff" 暂被移除。
  • 其他杂项:Kimi-K3图reserve修复、若干命名整理、测试避免部分架构的图重分配。

信息来源

llama.cpp Releases原始来源