ik_llama.cpp Bitnet FFN 融合优化解析:fused mul-silu 为 Metal/CUDA 再提约 1% 推理速度
2026/9/19 23:31:54 网站建设 项目流程

ik_llama.cpp Bitnet FFN 融合优化解析:fused mul-silu 为 Metal/CUDA 再提约 1% 推理速度

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

本文聚焦 ik_llama.cpp(llama.cpp 的一个高性能 fork)中针对 Bitnet 1-bit 模型的 FFN(前馈网络)优化:PR #110 将 fused mul-silu(融合乘法与 SiLU 激活)引入build_bitnet()图构建路径。文章从该 PR 的动机出发,结合 build_bitnet.cpp 与 ggml 核心算子实现,剖析融合算子的底层原理、调用链与后端支持情况,并给出可验证的性能结论边界。读完你将理解:为什么"融合"能省时、Bitnet 为什么绕过了标准 FFN 构建函数,以及该优化在 Metal、CUDA 后端上的实际收益。

背景:Bitnet 支持与 FFN 融合优化的由来

ik_llama.cpp 很早就加入了 Bitnet(1-bit / 1.58-bit 权重模型)的支持,并为此设计了专用的量化类型IQ1_BNIQ2_BN(带逐行 scale,可同时处理带/不带独立张量 scale 的 Bitnet 模型),详见同系列的 PR #106 Bitnet changes。

在常规架构上,作者 ikawrakow 已将"门控投影(gate)+ 上投影(up)+ 激活"的经典 SwiGLU 式 FFN 计算融合为一个算子,从而减少中间张量的写出与回读、降低内存带宽压力。这项融合默认通过标准 FFN 构建函数llm_build_ffn()自动生效。

然而,正如 PR #110 描述中所坦承的:

I had forgotten thatbuild_bitnet()does not use the standardllm_build_ffnfunction, so the fused mul-silu didn't get used automatically for Bitnet when I added it to llm_build_ffn.

即:作者在给llm_build_ffn加入 fused mul-silu 时,忘了 Bitnet 的图构建函数build_bitnet()并未走这条标准路径,导致融合优化对 Bitnet 并未自动生效。PR #110 正是补齐这个遗漏,为 Bitnet 的 FFN 显式接入 fused mul-silu。

问题根源:Bitnet 为何绕开llm_build_ffn

在 llama-build-context.cpp 中,llm_build_ffn()是绝大多数架构共用的 FFN 构建函数,它内部会根据权重类型、是否量化、fused_up_gate开关等条件自动选择最优路径。而 Bitnet 由于权重极度量化(1-bit 级别的IQ1_BN/IQ2_BN)且需要额外的 scale 缩放处理,其旧版图构建函数build_bitnet()在 build_bitnet.cpp 中被独立实现,FFN 部分手工展开,并未调用llm_build_ffn,因此默认享受不到新增的融合优化。

这正是 PR #110 要修复的"漏网之鱼"——优化逻辑加在了公共函数里,但 Bitnet 走的是私有路径。

fused mul-silu 的底层原理:GGML_OP_FUSED_MUL_UNARY

先看标准的 SwiGLU 式 FFN 计算(gate 路径):

tmp = up_matmul(x) // 上投影 gate = gate_matmul(x) // 门控投影 out = silu(gate) * tmp // 激活后逐元素相乘

未融合时,silu(gate)需要先生成一份完整的中间张量,再与tmp相乘,涉及额外的内存写入与读取。

融合后,ggml 提供GGML_OP_FUSED_MUL_UNARY算子:对两个同形张量先做逐元素乘法、再施加一元激活(SiLU、ReLU、GELU 等),一步完成silu(gate) * up。其核心实现在 ggml.c 的ggml_fused_mul_unary_impl()

GGML_ASSERT(ggml_is_contiguous(a)); // ...形状/算子合法性校验... result->op = GGML_OP_FUSED_MUL_UNARY; result->src[0] = a; // gate 结果(施加 unary) result->src[1] = b; // up 结果

该实现同样支持特例:当a->ne[0] == 1(即 gate 结果为行向量/标量行)时,可仅对b施加激活并原地融合(对应op == SILU || SIGMOID分支),进一步省去对b的复制。

更进一步,当 up 与 gate 权重均为量化类型且形状一致时,ggml 还提供ggml_fused_up_gate()(ggml.c):直接把两个矩阵乘up×bgate×b与激活融合进一个kernel(GGML_OP_FUSED_UP_GATE),连两个 matmul 结果张量的物化都省掉了。llm_build_ffn()内部正是优先走这条量化融合路径,否则回退到ggml_fused_mul_unary()

源码级剖析:build_bitnet中的融合调用点

从当前仓库源码看,build_bitnet.cpp 的 FFN 部分已经完成了 PR #110 所期望的融合改造,其关键步骤为:

struct ggml_tensor *tmp = ggml_mul_mat(ctx0, model.layers[il].ffn_up, cur); // ...ffn_up scale 处理... cur = ggml_mul_mat(ctx0, model.layers[il].ffn_gate, cur); // ...ffn_gate scale 处理... cur = ggml_fused_mul_unary(ctx0, cur, tmp, GGML_UNARY_OP_SILU); cb(cur, "ffn_gate_par", il);

即:ffn_upffn_gate两个投影各自完成 matmul 后,直接调用ggml_fused_mul_unary(..., GGML_UNARY_OP_SILU)一步完成"SiLU 激活 × up 结果"的融合计算,中间不再物化silu(gate)临时张量。随后依次经过ffn_sub_norm(缩放因子1/(ffn_up_scale²))、ffn_down投影,再与残差相加。

值得说明的架构差异:

  • 旧版 Bitnet(build_bitnet())激活函数为 SiLU,且 scale 通过权重张量op_params内嵌传递(如q_scaleffn_gate_scale),这正是它无法直接复用llm_build_ffn的原因之一;
  • 1.58-bit 新版(build_bitnet_158(),build_bitnet.cpp)则改用llm_build_ffn(..., LLM_FFN_RELU_SQR, LLM_FFN_PAR, ...),激活为 ReLU 平方,scale 使用独立的wo_scale/ffn_down_scale张量——这条路径天然走公共融合逻辑;
  • 另外,受限于 Bitnet 模型 100 维的"奇怪 head size",Flash Attention 无法启用,注意力部分使用标准llm_build_kv()(见 PR #106),这也是 Bitnet 图构建长期保持独立的原因之一。

后端支持与性能收益

GGML_OP_FUSED_MUL_UNARY已得到主流后端的原生支持,可在各自 dispatch 表中查到对应实现:

  • Metal:ggml-metal.m 与 Metal shader(ggml-metal.metal);
  • CUDA:ggml-cuda.cu 提供 kernel 实现;
  • Vulkan:ggml-vulkan.cpp 以ggml_vk_op_f32统一入口覆盖该算子。

关于收益,PR #110 的原话是:

This gives us another ~1% speedup for TG-128 on Metal and CUDA.

即该融合为TG-128(单次生成 128 token 的解码吞吐场景)在 Metal 与 CUDA 后端带来约 1% 的额外提速。这是作者在 RTX 4080 等环境下实测的增量收益——它建立在 Bitnet 已多轮优化(PR #106 中 TG-128 已从约 320 t/s 提升到 368 t/s)的基础之上,属于"锦上添花"的带宽优化红利,而非量级跃升。需要说明:1% 是作者在该 PR 中给出的实验数据,具体数值会随模型尺寸、量化类型(IQ1_BN/IQ2_BN)、后端与硬件环境而浮动。

小结

PR #110 是一个典型的"优化漏网修复"案例:融合算子的价值在于减少中间张量物化、降低内存带宽压力,而 Bitnet 这类独立图构建架构恰恰容易错过公共路径上的通用优化。当前仓库源码已确认build_bitnet()的 FFN 显式调用ggml_fused_mul_unary(ctx0, cur, tmp, GGML_UNARY_OP_SILU)(build_bitnet.cpp),且该算子在后端层由 Metal、CUDA、Vulkan 共同支持,最终为 Metal/CUDA 的 TG-128 场景带来约 1% 的实测提速。

对开发者而言,这条演进脉络也提供了可迁移的工程经验:当你在公共构建函数中引入新的融合优化时,务必逐一核对那些绕过公共路径的专属架构(如 Bitnet),否则优化可能静默失效

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询