
ik_llama.cpp GQA 模型 CPU 解码提速PR #332 的 TG 性能优化与 FA 收益分析【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本篇技术指南围绕 ik_llama.cpp 合并自 PR #332 的改进展开介绍该项目如何在纯 CPU 环境下针对 GQAGrouped-Query Attention架构模型LLaMA-2、Gemma 系列等提升 TGToken Generation逐 token 解码性能。文章完整复现了 PR 中使用的llama-sweep-bench基准方法与关键数据并结合仓库源码剖析K*Q与V*softmax(K*Q)矩阵乘法在 Flash AttentionFA开关下的线程分配差异帮助读者理解 GQA 模型在 CPU 上的计算瓶颈与优化思路同时掌握在 ik_llama.cpp 中复现这套基准实验的完整操作。背景GQA 模型在 CPU 解码阶段的性能瓶颈在多轮对话与长上下文的实际使用中模型的生成阶段TG是主要的耗时环节。对于 LLaMA-2 及之后的 LLaMA 系列、Gemma 系列等 GQA 架构模型每次生成一个 token 都需要执行两次关键的矩阵运算K*Q查询向量与键缓存KV cache的注意力分数计算V*softmax(K*Q)注意力分数加权后的值缓存聚合。这两个矩阵乘法的形状由「KV 头的数量」与「KV cache 中已缓存的 token 数N_KV」共同决定。当只生成 1 个 token 时Q 矩阵的行数等于 KV 头数而非全部注意力头数这使得传统上按 GEMV矩阵-向量乘方式调度的计算在头数较少、行数很少时无法充分利用多核 CPU 的并行能力。在 PR #332 之前ik_llama.cpp 的 CPU 路径对这两个乘法在开启与关闭 FA 时的收益并不均衡PR #332 的核心思路是改变K*Q与V*softmax(K*Q)两个矩阵乘法之间的线程threads分配方式将计算更合理地分摊到各线程上从而在 GQA 模型上获得 TG 性能提升。从仓库当前的注意力构建代码可以看到这两个乘法位于 build_llama.cpp 的llm_build_kv与build_std_attention调用链中KQ_mask、kq_scale、kv_head、n_kv等参数均在此处汇聚PR 正是围绕这一调用链的 CPU 后端实现进行的调度调整。优化效果概述FA 在 TG 阶段由劣势转为优势根据 PR 描述改进带来的收益分两种情况未启用 FAno-FA性能提升相对较小主要来源于上述线程分配的重新平衡启用 FAFlash Attention性能提升非常显著且FA 在 TG 阶段首次全面超越 no-FA。这一结论很重要此前在部分场景中 FA 主要被视作提示词处理PP阶段的加速手段而 TG 阶段往往因为单 token 的形状限制而收益有限。PR #332 之后GQA 模型在 CPU 上开启 FA 进行解码同样能获得可观收益。从实现层面看ik_llama.cpp 的 FA 路径由ggml_flash_attn_ext提供例如 build_deepseek2.cpp 中ggml_flash_attn_ext_set_prec(kqv, GGML_PREC_F32)的用法其开关由cparams.flash_attn控制最终通过公共参数-fa, --flash-attn (auto|on|off|0|1)暴露给用户参数解析位于 common/common.cpp 的选项表第 3079-3080 行同时支持环境变量LLAMA_ARG_FLASH_ATTN与-no-fa, --no-flash-attn反向关闭。FA 关闭时若 V-cache 为量化类型框架还会强制将 V-cache 视作 f16 处理common/common.cpp 中if (!cparams.flash_attn ggml_is_quantized(cparams.type_v))的检查这与 PR 中「V-cache 在未启用 FA 时为 f16」的测试前提完全对应。基准方法使用 llama-sweep-bench 复现实验PR 作者使用仓库自带的llama-sweep-bench工具完成全部测量其源码位于 examples/sweep-bench/sweep-bench.cpp说明文档见 examples/sweep-bench/README.md。工具原理sweep-bench与普通 benchmark 的区别在于它不是对整个上下文长度做一次平均而是按 ubatch 大小的窗口对整个上下文做扫描在每个窗口内分别测量 PP 与 TG 性能从而绘制「性能随上下文长度N_KV变化」的曲线。其基准流程摘自 README为为上下文中每个 ubatch 大小的窗口生成ubatch/4个 token节省时间不生成整个窗口测量生成TG性能将生成的 token 从 KV cache 中移除准备一批ubatch大小的随机 token处理该批次PP测量提示词处理PP性能。复现命令PR 中给出的完整命令为./bin/llama-sweep-bench -m $model -c 10240 -ctk q8_0 -ctv q8_0 -t 32 -fa各参数含义如下参数含义PR 测试取值-m, --model模型文件路径GGUFLLaMA-3.1-8B-Instruct / Gemma3-12B-Instruct均Q4_0量化-c, --ctx-size上下文总长度10240后续复跑扩展到 16k-ctk, --cache-type-kK-cache 量化类型q8_0-ctv, --cache-type-vV-cache 量化类型q8_0no-FA 时实际按 f16 生效-t, --threadsCPU 线程数32-fa, --flash-attn是否启用 Flash Attention启用sweep-bench每次运行会以 JSON 行格式输出每个窗口的测量结果字段包括n_kv_max最大上下文、n_batch、n_ubatch、flash_attn、n_threads、pp每窗口 PP token 数、tg每窗口 TG token 数、n_kv当前 KV cache 大小、t_pp/speed_pp/t_tg/speed_tg等对应输出代码位于 sweep-bench.cpp 第 416-425 行。仓库还提供了绘图脚本 examples/sweep-bench/sweep-bench-plot.py可将多次运行的 JSON 结果按label分组绘制 PP/TG 吞吐量随n_kv变化的误差条曲线——PR 讨论中用户正是用该脚本生成了对比曲线并定位了两实现的「交叉点」。测试环境CPUAMD Ryzen 5975WXvanillaAVX2即未启用 AVX-512 的常规 x86-64 路径模型LLaMA-3.1-8B-Instruct 与 Gemma3-12B-Instruct均以Q4_0量化KV cacheQ8_0K 与 VFA 关闭时 V-cache 实际为f16对照组主线 llama.cppbuild 5139开启 FA 的结果。需要说明的是作者在讨论中指出该测试机对运行间缓存page cache的丢弃与否较为敏感且结果对 KV cache 分配量也有一定依赖因此不同批次运行之间存在小幅波动本文所列数据均为 PR 讨论中作者亲测的原始输出具体数字会随硬件、量化组合与上下文分配而变不应视为绝对性能结论。结果分析LLaMA-3.1-8B-Instructhead size 128在 16k 上下文的复跑中ik_llama.cpp 与主线 llama.cpp 的对比数据如下PP512, TG128S_TG为生成速度 t/s主线 llama.cppbuild 5139FA 开启PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s51212802.737187.047.54816.965121285123.185160.767.95316.0951212810243.721137.608.40915.2251212815364.219121.358.82614.5051212820484.711108.689.19913.9151212825605.20698.349.59213.3451212830725.70489.769.98012.8351212835846.25281.8910.37012.3451212840966.86774.5510.76511.8951212846087.50768.2011.15711.4751212851208.23162.2111.55211.0851212856329.21455.5711.94110.72512128614410.46748.9112.33010.38512128665611.64643.9612.71310.07512128716813.10439.0713.1099.76512128768014.81334.5613.5009.48512128819216.57030.9013.8859.22512128870418.24628.0614.2778.97512128921620.14225.4214.6758.72512128972821.72923.5615.0728.495121281024023.61521.6815.4548.285121281075225.40620.1515.8408.085121281126427.29918.7616.2367.885121281177629.12217.5816.6257.705121281228831.07916.4717.0127.525121281280033.05215.4917.4077.355121281331234.95814.6517.7967.195121281382437.17013.7718.1887.045121281433639.42512.9918.5706.895121281484841.66112.2918.9596.755121281536043.76611.7019.3506.625121281587246.12911.1019.7306.49ik_llama.cppFA 开启PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s51212801.638312.567.73916.545121285121.661308.287.85216.3051212810241.705300.357.96116.0851212815361.766289.908.07515.8551212820481.806283.528.17015.6751212825601.860275.348.26115.5051212830721.914267.518.36315.3151212835841.981258.458.46815.1151212840962.022253.228.59214.9051212846082.076246.618.70614.7051212851202.132240.128.80014.5551212856322.189233.928.90214.3851212861442.240228.588.99814.2351212866562.298222.819.09314.0851212871682.352217.669.19113.9351212876802.407212.699.29713.7751212881922.462207.929.40913.6051212887042.519203.229.51413.4551212892162.573199.029.61913.3151212897282.630194.719.70213.19512128102402.683190.829.79613.07512128107522.739186.919.90412.92512128112642.795183.1910.01812.78512128117762.851179.6210.12412.64512128122882.905176.2410.22812.51512128128002.963172.7810.32112.40512128133123.018169.6410.41312.29512128138243.078166.3410.53812.15512128143363.133163.4310.63212.04512128148483.192160.4010.73811.92512128153603.249157.6110.83811.81512128158723.305154.9110.94211.70从数据可以读出两个关键点PP 差距巨大ik_llama.cpp 的 PP 从零上下文时的 312.56 t/s 衰减到 16k 时的 154.91 t/s而主线从 187.04 t/s 暴跌至 11.10 t/s。在 16k 时主线 PP 仅为 ik_llama.cpp 的约 7.2%约 14 倍差距。作者据此解释了其在别处对 DeepSeek-V3/R1 观察到的「PP 性能 6 倍下滑」的惊讶——相比之下 ik_llama.cpp 在 16k 时 PP 仅下降约 2 倍LLaMA-3.1至 2.3 倍Gemma3。TG 差异在零上下文时两者 TG 几乎相同16.96 vs 16.54 t/s但随着N_KV增长主线的生成速度衰减更快到 16k 时主线 S_TG6.49 t/s仅为 ik_llama.cpp11.70 t/s的约 55.5%。不过两条曲线呈现逐渐收敛的走势说明差距主要来自对长 KV cache 的调度效率而非短上下文的峰值能力。作者还指出主线为优化 head size 256 场景所做的改动对 head size 128最常见的 head size产生了严重的负面效果这正是两个实现在不同架构模型上表现迥异的重要原因。结果分析Gemma3-12B-Instructhead size 2568 个 KV 头作者在另一组运行中给出了 Gemma3-12B-Instruct 的完整数据同样Q4_0模型、Q8_0KV cache、FA 开启主线 llama.cppbuild 5139FA 开启PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s51212804.669109.6712.16410.525121285124.811106.4213.0619.8051212810245.049101.4013.8189.2651212815365.16499.1513.9609.1751212820485.28096.9714.1079.0751212825605.42394.4014.2488.9851212830725.61991.1114.3958.8951212835845.82387.9214.5358.8151212840966.07084.3514.6778.7251212846086.30681.1914.8258.6351212851206.54778.2014.9698.5551212856326.89074.3115.1318.4651212861447.22770.8515.2818.3851212866567.51368.1515.3948.3251212871687.91864.6715.5378.2451212876808.33461.4315.6808.1651212881928.80058.1815.8308.0951212887049.20055.6515.9718.0151212892169.52353.7616.1017.95512128972810.04850.9516.2427.885121281024010.49548.7816.3717.825121281075210.95546.7316.5077.755121281126411.37545.0116.6627.685121281177611.83743.2616.7987.625121281228812.32041.5616.9497.555121281280012.61340.5917.0857.495121281331212.81539.9517.2087.445121281382413.10039.0817.3647.375121281433613.46638.0217.5187.315121281484813.66937.4617.6557.255121281536013.78937.1317.7977.195121281587213.87436.9017.9377.14ik_llama.cppFA 开启PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s51212802.593197.4612.30110.415121285122.662192.3412.50110.2451212810242.756185.7712.70310.0851212815362.854179.4212.9469.8951212820482.946173.7813.1439.7451212825603.040168.4213.3319.6051212830723.136163.2613.5079.4851212835843.235158.2513.7119.3451212840963.336153.4813.9079.2051212846083.432149.2014.0889.0951212851203.530145.0514.2908.9651212856323.632140.9914.4838.8451212861443.729137.3114.6738.7251212866563.834133.5314.8798.6051212871683.934130.1415.0748.4951212876804.046126.5515.2668.3851212881924.140123.6715.4438.2951212887044.243120.6615.6168.2051212892164.342117.9115.8388.0851212897284.450115.0616.0088.00512128102404.552112.4816.1977.90512128107524.721108.4616.4297.79512128112644.762107.5116.6227.70512128117764.869105.1616.8237.61512128122884.973102.9616.9827.54512128128005.077100.8417.2087.44512128133125.17598.9317.4197.35512128138245.27897.0217.6037.27512128143365.46193.7517.7987.19512128148485.56092.0819.1267.12512128153605.71789.5519.3837.06512128158725.89186.9119.6407.00Gemma3 数据呈现出与 LLaMA-3.1 不同的对比格局TG 差距明显缩小在 16k 上下文主线 TG7.14 t/s甚至略优于 ik_llama.cpp7.00 t/s两条 TG 曲线在中长上下文处存在交叉点这正是讨论中用 sweep-bench-plot.py 绘制的图所呈现的现象。作者在首轮测试中多次复跑主线并切换缓存策略确认主线「前 1024 token 出现突发性能下降」的现象稳定存在。PP 依旧全面领先ik_llama.cpp 的 PP 从零上下文的 197.46 t/s 衰减到 16k 的 86.91 t/s约 55.5% → 42.4% 的下降幅度而主线从 109.67 t/s 跌至 36.90 t/s两者始终存在明显差距。为什么 Gemma3 的 TG 增益小于 LLaMA-3作者给出了清晰的解释这也是理解 PR #332 收益边界的核心Gemma3 总共 16 个注意力头、8 个 KV 头。在 TG 阶段生成单个 token 时K*Q与V*softmax(K*Q)两个 GEMM 的矩阵只有2 行而 LLaMA-3 系列在该阶段对应矩阵为4 行PR #332 的本质收益来自「把原本按 GEMV 处理的运算升级为多行 GEMM 并按行分配线程」。行数越多GEMM 相对 GEMV 的并行优势越明显。当只有 2 行时该收益自然被摊薄。此外Gemma3 的 head size 为 256LLaMA-3 为 128而主线 llama.cpp 的 CPU 代码在 PR 讨论时已经过多轮改动其中针对 head size 256 场景的优化在 Gemma3 上表现出相对优势但对 head size 128 的模型如 LLaMA-3 系列却造成了明显的性能拖累。作者明确表示「主线的 CPU 代码在我离开项目后有大量变化我无法确定其具体实现」因此对主线的内部机制不做断言。从 ik_llama.cpp 的架构构建代码可以佐证 head size 与注意力形状如何影响 GEMM 维度在 build_llama.cpp 中n_embd_head hparams.n_embd_head_v(0)与n_embd_head_k(0)被断言相等注意力分数缩放系数kq_scale取1/sqrtf(n_embd_head)KV 头维度kv_head、n_kv直接决定llm_build_kv中两个乘法张量的形状。这些参数在不同架构间差异很大直接决定线程分配优化的收益上限。实践建议与结论综合 PR #332 的描述与数据可以得出以下可用于实际部署的结论GQA 模型在 CPU 上应优先开启 FAPR 之后 FA 在 TG 阶段已全面超越 no-FA使用-fa或环境变量LLAMA_ARG_FLASH_ATTN即可启用KV cache 使用q8_0量化-ctk q8_0 -ctv q8_0在保持精度的同时显著减少显存/内存占用且与 FA 路径兼容注意 FA 关闭时 V-cache 会被框架按 f16 处理common/common.cpp 第 4304、4393 行的类型检查收益与模型形状强相关KV 头越少、TG 阶段 GEMM 行数越少本优化的绝对收益越小Gemma3 的 2 行 vs LLaMA-3 的 4 行head size 128 的主流模型LLaMA-2/3 系收益最大复现实验请使用 sweep-bench它按窗口扫描上下文能清晰呈现性能随N_KV的衰减曲线配合 sweep-bench-plot.py 可做多实现对比测试时注意缓存状态与 KV cache 分配量对结果的扰动结论有边界本文数据来自单一 CPURyzen 5975WXAVX2与特定量化组合不同硬件AVX-512、AMX、ARM和量化如Q8_0、IQ4_KS下的相对差距会变化建议在目标机器上自行复测。PR #332 的价值不仅在于一组漂亮的数字更在于它揭示了 CPU 解码优化的一个关键维度在 GQA 架构下K*Q与V*softmax(K*Q)的线程分配方式直接决定多核利用率。对于希望深入理解 llama.cpp 系 CPU 后端调度逻辑的读者可以从 src/graphs 目录下各架构的build_*文件入手对照 build_llama.cpp 的注意力构建流程并结合 examples/sweep-bench 的测量工具验证自己的改动。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考