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_BN、IQ2_BN(带逐行 scale,可同时处理带/不带独立张量 scale 的 Bitnet 模型),详见同系列的 PR #106 Bitnet changes。
在常规架构上,作者 ikawrakow 已将"门控投影(gate)+ 上投影(up)+ 激活"的经典 SwiGLU 式 FFN 计算融合为一个算子,从而减少中间张量的写出与回读、降低内存带宽压力。这项融合默认通过标准 FFN 构建函数llm_build_ffn()自动生效。
然而,正如 PR #110 描述中所坦承的:
I had forgotten that
build_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×b、gate×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_up与ffn_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_scale、ffn_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),仅供参考