ik_llama.cpp PR #181 解析llama-bench 长上下文生成基准、repacked 量化行对齐修复与 Q8_0 KV 缓存 Flash Attention 支持【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本篇技术文章基于仓库 github-data/pull_requests/181 - Various.md 中记录的 PR #181 展开。这是一个典型的由一个测试需求牵出两个底层缺陷的综合修复 PR作者 ikawrakow 先为llama-bench新增-gp选项以量化长提示词之后的 Token 生成TG性能随后在测试过程中接连发现并修复了 repacked行交织重打包量化在非 128 倍数行大小张量上的兼容性问题以及 Flash AttentionFA在 head size 小于 128 时Q8_0K-cache 无法工作的问题。读完本文你将掌握llama-bench四种测试模式PP/TG/PG/GP的选型与-gp的用法、ik_llama.cpp 行交织重打包格式的对齐约束及其底层原因以及量化 KV 缓存与 Flash Attention 的 head size 适配关系。PR #181 背景一次测试需求引发的三连修复PR #181作者ikawrakow创建于 2025-01-29状态 Closed的出发点非常朴素作者想要测试长提示词处理之后的 Token 生成性能TG performance after a long prompt以便与上游 llama.cpp 中的 MLAMulti-head Latent Attention注意力实现做对比。为此作者参考上游 llama.cpp 的 PR #11126为llama-bench加入了-gp选项。但在后续验证中两个隐藏问题被逐一暴露并修复重打包repack量化缺陷行交织重打包后的Q8_0、Q4_0量化张量对行大小不是 128 的倍数的张量无法正常工作而作者用来测试的 Deepseek2-Lite 中恰好存在这类张量Flash Attention 缺陷在Llama-3.2-1Bhead size 为 64上对比性能时发现 FA 搭配Q8_0K-cache 不工作而代码注释中早有Q8_0在 head size 小于 128 时不工作的提示。这个 PR 很好地体现了基准测试工具与底层计算内核之间的闭环基准工具本身需要足够的灵活性才能暴露真实场景的性能而真实的性能对比又会反过来逼出内核实现的边界缺陷。新增-gp选项测量长提示词之后的生成性能从-pg到-gp两种复合测试模式的差异在-gp出现之前llama-bench已有-pg pp,tg复合模式。二者都接受pp,tg形式的成对参数但语义截然不同。对照 examples/llama-bench/llama-bench.cpp 中的test_kind_type枚举可以看得非常清楚enum test_kind_type { // measure mean prompt processing rate without token generation TEST_KIND_PP, // measure mean token generation rate without prompt processing TEST_KIND_TG, // measure mean prompt processing and token generation rate TEST_KIND_PG, // measure mean token generation rate after processing prompt of given length TEST_KIND_GP, };-pg pp,tgTEST_KIND_PG同时统计pp 个 token 的 prompt 处理速率与 tg 个 token 的生成速率两者都在计时范围内测的是平均端到端吞吐-gp pp,tgTEST_KIND_GP先处理 pp 个 token 的提示词不计时再开始计时并生成 tg 个 token测的是长提示词处理完毕、KV 缓存已被填满之后的稳态生成速率。命令行解析与实例构造-gp的参数解析位于 examples/llama-bench/llama-bench.cpp与-pg完全对称} else if (arg -gp) { if (i argc) { invalid_param true; break; } auto p string_splitstd::string(argv[i], ,); if (p.size() ! 2) { invalid_param true; break; } params.n_gp.push_back({ std::stoi(p[0]), std::stoi(p[1]) }); }参数要求恰好两个以逗号分隔的整数缺失或数量不对都会报invalid_param。解析结果存入params.n_gp随后在 测试实例构造处 被展开为独立的cmd_params_instancefor (const auto n_gp : params.n_gp) { if (n_gp.first 0 n_gp.second 0) { continue; } cmd_params_instance instance { /* .test_kind */ TEST_KIND_GP, /* .model */ m, /* .n_prompt */ n_gp.first, /* .n_gen */ n_gp.second, ... }; instances.push_back(instance); }注意n_gp.first对应n_prompt先处理的提示词 token 数n_gp.second对应n_gen计时的生成 token 数当pp 0 tg 0时该实例会被跳过。在输出结果中GP 模式的测试名以tg%dpp%d形式标识llama-bench.cpp例如tg1284096表示处理完 4096 token 提示词后生成 128 token。-gp的用法帮助文本位于 llama-bench.cpp-gp pp,tg (default: 0,0)计时边界prompt 处理不计入生成耗时GP 模式的关键在于计时的起点。相关逻辑位于 llama-bench.cppif (t.n_prompt 0) { test_prompt(ctx, t.n_prompt, 0, t.n_batch, t.n_threads.second); } if (t.test_kind TEST_KIND_GP) t_start get_time_ns(); test_gen(ctx, t.n_gen, t.n_prompt, t.n_threads.first);即先用test_prompt把指定长度的提示词灌入上下文填充 KV 缓存、触发相关权重张量加载只有在TEST_KIND_GP时才在 prompt 处理完成之后才启动计时器随后test_gen的耗时才是被统计的对象。这保证了测量结果反映的是上下文已就绪状态下的稳态生成速度而不是包含 prompt 处理开销的混合值。实战用法组合使用-gp与 KV 缓存量化选项-ctk/-ctvcache-type-k/v见 llama-bench.cpp可以对比不同 KV 缓存类型在长上下文下的生成性能这正是 PR 作者用来评估 MLA 注意力实现上游 #11446的测试方法。例如# 处理 4096 token 提示词后计时生成 128 个 token使用 Q8_0 K-cache F16 V-cache llama-bench -m model.gguf -gp 4096,128 -ctk q8_0 -ctv f16 # 对比不同 K-cache 类型在长上下文下的 TG 性能 llama-bench -m model.gguf -gp 4096,128 -ctk f16 -ctv f16 llama-bench -m model.gguf -gp 4096,128 -ctk q8_0 -ctv f16llama-bench支持对同一参数用逗号分隔指定多个值见 llama-bench.cpp因此可以一次性跑多组对比。注意-gp的默认值为0,0等价于不启用该模式。修复一repacked 量化在非 128 倍数行大小下的兼容性什么是 repacked行交织重打包量化ik_llama.cpp 在标准 GGML 量化类型基础上引入了一批带_R4/_R8后缀的行交织row-interleaved重打包类型通过改变数据在内存中的排列方式提升 CPU 侧 matmul 的缓存友好度。类型映射定义在 src/llama-quantize.cpp例如{ GGML_TYPE_Q4_0_R8, { GGML_TYPE_Q4_0, 8} }, { GGML_TYPE_Q8_0_R8, { GGML_TYPE_Q8_0, 8} }, { GGML_TYPE_Q4_K_R4, { GGML_TYPE_Q4_K, 4} }, { GGML_TYPE_Q8_K_R8, { GGML_TYPE_Q8_0, 8} }, { GGML_TYPE_Q8_KV_R8, { GGML_TYPE_Q8_KV, 8} }, ...每个条目表示将标准类型Q8_0以行因子 8 重打包为Q8_0_R8。这些类型之间是数学等价对无损重打包的区别仅在内存布局因此可以在不损失精度的前提下获得更快的 CPU 计算路径。缺陷行大小必须对齐到 4 × block_sizePR 描述指出repacked 的Q8_0和Q4_0量化要求张量的行row大小为 128 的倍数。原因在于行交织因子与量化块尺寸的乘积Q8_0/Q4_0的 block size 均为 32乘以行因子 4_R4的因子或经过设计的交织结构后正好要求行大小是4 × 32 128的倍数。不满足该对齐条件时重打包内核无法正确处理张量。作者测试用的Deepseek2-Lite中恰好存在部分行大小不是 128 倍数的张量因此问题被直接触发——重打包后的模型无法正常加载或计算。修复落点修复后的重打包逻辑集中在 src/llama-quantize.cpp 的only_repack路径与 张量逐个处理处if (params-only_repack) { ggml_type repacked_type (ggml_type)iqk_repacked_type(tensor); bool modify !is_repacked iqk_should_modify_tensor(tensor); ... if (modify || repacked_type ! tensor-type) { new_type repacked_type; ... if (repacked_type ! tensor-type) { iqk_repack_tensor(aux_tensor); GGML_ASSERT(aux_tensor.type repacked_type); } } }关键点在于修复前重打包假设所有张量行大小都满足 128 的倍数修复后对不满足对齐条件的张量会走不同的分支保持原类型或采用兼容处理从而让 Deepseek2-Lite 这类模型也能安全使用重打包格式。iqk_repack_tensor的调用在 src/llama.cpp 中也能看到非 mmap 加载路径下的主机侧原地重打包if (!ml.use_mmap ml.repack_tensors) { int n_repacked 0; ... iqk_repack_tensor(it.second); if (it.second-type ! orig_type) n_repacked; ... if (n_repacked 0) LLAMA_LOG_INFO( Repacked %d tensors\n, n_repacked); }重打包的启用方式除了llama-quantize的--only-repack之外ik_llama.cpp 还提供了运行时重打包选项-rtr, --run-time-repack在llama-bench中同样可用见 llama-bench.cpp即在加载模型时于内存中现场把标准量化张量转为行交织布局。仓库文档 docs/development/on-demand-tensor-reload.md 对相关机制有进一步说明。需要注意的是llama-reload.cpp中的日志明确指出src/llama-reload.cpp-rtr对主机张量做的是原地重打包热加载恢复后的张量无法复现重打包状态F16 → BF16_R16 甚至是有损的因此重打包与热重载组合使用时需要留意一致性。修复二Flash Attention 对 head size 64 的Q8_0K-cache 支持缺陷场景Llama-3.2-1B 的 head size 为 64作者修复完重打包问题后在Llama-3.2-1B上对比性能时发现FA 搭配Q8_0K-cache 无法工作。原因在于Llama-3.2-1B的注意力 head size 是 64而代码中早已存在注释说明Q8_0在 head size 小于 128 时不支持。修复落点FA kernel 实例化的 head size 64 分支Flash Attention 的向量化实现通过宏展开为不同 (head size, K 类型, V 类型) 组合的 kernel 实例见 ggml/src/ggml-cuda/fattn-vec-f16.cuFATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q4_0) FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q4_1) FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q5_0) FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q5_1) FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q8_0) FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_F16 )第 26 行正是本 PR 修复的关键FATTN_VEC_F16_CASE( 64, GGML_TYPE_F16, GGML_TYPE_Q8_0)为 head size 64 补齐了Q8_0K-cache 的 kernel 实例。对应的外部声明同步更新在 ggml/src/ggml-cuda/fattn-vec-f16.cuh。也就是说修复后 head size 64 的模型如 Llama-3.2-1B在 K-cache 使用Q8_0、V-cache 使用Q4_0/Q4_1/Q5_0/Q5_1/Q8_0/F16时都有对应的 FA kernel 可供调度不再退回慢速路径或直接失败。需要说明的是当前仓库中 head size 64 的 FA 组合仍以K 侧为 F16、V 侧量化为主见 fattn-vec-f16.cu 的宏列表而FATTN_VEC_F16_CASE(128, ...)覆盖了 Q8_0 等 K 侧量化组合未实例化的组合会在 fattn-common.cuh 中报出 Unsupported KV type combination for head_size ... 并回退。这类报错信息也是判断当前构建是否支持特定 KV 组合的直接线索。实测用法在 CUDA 后端验证该修复可用llama-bench的-faflash-attn与-ctk组合对 head size 64 的模型对比量化 K-cache 前后的生成性能# head size 64 的模型如 Llama-3.2-1BQ8_0 K-cache FA llama-bench -m llama-3.2-1b.gguf -fa 1 -ctk q8_0 -ctv f16 -gp 4096,128 # 对照组F16 K-cache FA llama-bench -m llama-3.2-1b.gguf -fa 1 -ctk f16 -ctv f16 -gp 4096,128-gp与-fa/-ctk的组合恰好构成了 PR 作者当初的完整工作流先用 GP 模式得到长上下文下的真实生成速率再通过 KV 缓存量化对比验证 FA 内核的适配情况。小结一个 PR 的三重价值PR #181 虽然以 Various 命名内容却高度聚焦值得关注三层价值工具层-gp补齐了llama-bench缺少的长提示词后稳态生成性能测量维度与-pg形成互补是评估长上下文场景如 MLA 注意力、长对话服务时的重要基准手段实现与用法可参考 examples/llama-bench/llama-bench.cpp 与 llama-bench README量化层修复了行交织重打包格式对行大小必须为 128 倍数的隐含依赖使 Deepseek2-Lite 等张量形状不规整的模型也能安全使用Q8_0_R8/Q4_0_R8等重打包类型相关类型表与逻辑见 src/llama-quantize.cpp内核层为 head size 64 的模型补齐了Q8_0K-cache 的 Flash Attention kernel 实例消除了小 head size 模型 量化 KV 缓存这一组合的盲区实例化列表见 ggml/src/ggml-cuda/fattn-vec-f16.cu。三个修复环环相扣没有-gp长上下文生成性能无法被准确定量没有重打包对齐修复测试模型根本跑不起来没有 FA 的 head size 适配量化 KV 缓存的性能收益也无从谈起。对于想深入理解 ik_llama.cpp 基准工具链、行交织量化布局与 Flash Attention kernel 分派机制的读者而言PR #181 是一个信息密度极高的切入点。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考