Metal: ~6% token-generation regression on gemma-4-26B-A4B since #28164 (fusion packing chains patterns, reducing concurrency)

在 Apple Silicon(Metal 后端)上用 llama.cpp 跑 Gemma 26B 系列模型做 token 生成时,若发现相对 b10908 及之前版本速度下降约 6–9%,通常是自 #28164(b10909 起)引入的 fusion packing 变更把多个 pattern 串

快速结论:在 Apple Silicon(Metal 后端)上用 llama.cpp 跑 Gemma 26B 系列模型做 token 生成时,若发现相对 b10908 及之前版本速度下降约 6–9%,通常是自 #28164(b10909 起)引入的 fusion packing 变更把多个 pattern 串成一个 pack,导致 concurrency pass 可用节点变少。优先排查是否命中了 ggml_metal_fusion_max() 的多 pattern 链接逻辑。

适用环境:macOS 26.5.1(Darwin arm64),AppleClang 21.0.0 / Xcode 26.6,CMake 4.4.3、Ninja 1.13.2;llama.cpp 版本 0.4.1-dev(tag b11050,commit 60b06ab 一类的构建),编译选项 -DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON;硬件 Apple M2 Ultra 128 GB / M3 Ultra 96 GB;模型 gemma-4-26B-A4B-it-Q4_K_M.gguf(MoE 26B/A4B)或 Ollama gemma4:26b-a4b-it-qat(Q4_0)。

最快修复方案:Issue 中已验证:让 ggml-metal-fusion.cppggml_metal_fusion_max() 每次调用只打包一个 pattern(=一个 fused kernel),不再把相邻 pattern 串成一个 pack。报告者在 M2 Ultra 与 M3 Ultra 上均复现出恢复,单项 pattern packing 后速度回到父版本约 0.14% 以内。

注意事项:该修复为源码级改动,需要自行重新编译;Issue 中未确认是否已合入上游正式版本,也未有官方 follow-up PR 指针,因此作为补丁使用时需关注是否与当前 master 代码冲突。合入后需重新验证 prompt processing 是否受影响(Issue 中 pp 不受该 commit 影响)。

问题场景

在 Apple Silicon Mac 上使用 llama.cpp 的 Metal 后端运行 Gemma 26B 系列模型(如 gemma-4-26B-A4B-it-Q4_K_M.gguf 或 Ollama 的 gemma4:26b-a4b-it-qat)进行 token 生成。复现命令为 llama-bench -m gemma-4-26B-A4B-it-Q4_K_M.gguf -p 512 -n 128 -r 3,也可在 llama-server 中用固定 prompt 连续请求测算 timings.predicted_per_second。问题自 PR #28164(b10909,commit a2878d30)起出现,Prompt processing 不受影响。

报错原文

Metal: ~6% token-generation regression on gemma-4-26B-A4B since #28164 (fusion packing chains patterns, reducing concurrency)

原因分析

PR #28164 重写了 Metal fusion 表,其中 ggml_metal_fusion_max() 增加了 while (i_f < n_idxs && total < GGML_METAL_FUSION_MAX) 循环,把多个 ggml_can_fuse 链背靠背打包进同一个 pack。旧实现(ggml-metal-common.cpp)每次只打包一条融合链。

在这个模型上表现为:MoE 输出处的 ADD x7 被与下一个 block 的 RMS_NORM+MUL 粘进同一个 pack(f=9);同一层的两条分支 norm(ffn_mlp 与 ffn_moe)由原本两个独立 pack 合并为一个 pack(f=4)。gemma4-26B-A4B 每层同时有 dense MLP 分支和 MoE 分支,把两条分支的输入 norm 粘进同一个 pack 后,损失了 concurrency pass 原本拥有的重排自由度,因此参与 (concurrent) 编码的节点从 720 降到 662,token 生成性能随之下降。编码出的 kernel 本身没变(9 个节点的 pack 仍被编码为 ADD x7 + RMS_NORM+MUL),改变的只是 packing 粒度。使用 GGML_METAL_FUSION_DEBUG=2 可确认各 pattern 匹配计数在改动前后完全一致,说明不是 pattern 停止匹配导致。

环境排查

  • 确认 llama.cpp 版本/commit:b10908 为最后正常版本,b10909(= a2878d30,PR #28164)为首个异常版本;b11050 仍存在该问题。
  • 确认 macOS 版本(报告为 macOS 26.5.1)、Xcode / AppleClang 版本(AppleClang 21.0.0 系列)。
  • 确认 Metal 后端是否启用(-DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON)。
  • 确认硬件为 Apple Silicon(M2 Ultra 128 GB 或 M3 Ultra 96 GB)。
  • 确认模型为 Gemma 26B 系列(gemma-4-26B-A4B-it-Q4_K_M.gguf 或 QAT Q4_0),其他模型(如 Qwen3.8-Flash-Next、gemma-4-e4b)报告未受影响。
  • 可用对比开关定位:GGML_METAL_CONCURRENCY_DISABLE=1GGML_METAL_GRAPH_OPTIMIZE_DISABLE=1GGML_METAL_FUSION_DISABLE=1 以及 GGML_METAL_GRAPH_DEBUG=1 查看 concurrent 节点数。

解决步骤

  1. 先做基线复现:在 b10908 与 b10909 之间用 llama-bench -m gemma-4-26B-A4B-it-Q4_K_M.gguf -p 512 -n 128 -r 3 对比 tg128,确认存在约 6% 的差距。
  2. GGML_METAL_GRAPH_DEBUG=1 统计 decode graph 中被标记为 (concurrent) 的节点数,正常版本约 720,异常版本约 662。
  3. 打开 ggml/src/ggml-metal/ggml-metal-fusion.cpp,定位 ggml_metal_fusion_max() 中把相邻 pattern 累加进同一 pack 的 while 循环、无匹配时的检查以及最终返回逻辑。
  4. 改为每次只打包一个 pattern(即一个 fused kernel),不再向后链接多个 pattern:对应报告中的 diff 思路是去掉 total / i_f 累加循环,仅调用一次 ggml_metal_fusion_next() 并据此返回长度。
  5. 用同一套 Xcode 工具链、CMake/Ninja 重新编译(Release、GGML_METAL=ONGGML_METAL_EMBED_LIBRARY=ON)。
  6. 在同一模型上重新跑 llama-benchllama-server 计时,确认 tg128 回到 b10908 附近水平。

验证方法

重新编译后运行与之前相同的 llama-bench -p 512 -n 128 -r 3,观察 tg128 是否恢复到 b10908 的约 85 t/s 或 M3 Ultra 上父版本的约 102 t/s 水平;同时检查 GGML_METAL_GRAPH_DEBUG=1 输出的 (concurrent) 节点数是否回到约 720。报告者在 M3 Ultra 上验证单品 pattern packing 的恢复结果与父版本仅差 0.14%,且生成文本与 token 序列保持一致(text SHA-256 未变),说明修复不改变生成语义。

参考来源

ggml-org/llama.cpp #29134

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 25208

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注