ik_llama.cpp PR 517 解析:IQ1_S 量化 CPU 提示处理速度近翻倍背后的矩阵乘法内核优化
2026/9/20 12:53:25 网站建设 项目流程
  • 人工智能
  • 大模型
  • 推理引擎
  • 本地部署
  • 模型量化
  • 模型优化

【免费下载链接】ik_llama.cpp

llama.cpp fork with additional SOTA quants and improved performance

项目地址:https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
点击查看免费下载

本文基于 ik_llama.cpp 仓库中 github-data/pull_requests/517 - IQ1_S_ much faster CPU prompt processing.md 所记录的 PR 内容展开,并结合仓库内ggml/src/iqk/的 IQK 量化矩阵乘法内核源码、ggml.c的类型注册表、sweep-bench基准工具与llama-quantize量化入口,完整还原这次针对 1-bit 级量化类型IQ1_S的 CPU 提示处理(prompt processing, PP)提速方案。读完本文,你将理解IQ1_SIQ1_S_R4两种极低比特量化类型的内存布局差异、加速内核的注册与分发机制,以及如何用仓库自带的基准工具复现"PP 吞吐接近翻倍"的实测数据。

PR 背景:一条贯穿"非交错量化类型"的提速技术路线

PR 517 的标题是"IQ1_S: much faster CPU prompt processing",由仓库作者ikawrakow于 2025-06-11 提交并关闭(State: Closed)。它的定位非常明确——文档原文指出:

This PR is a follow up of #515 and #516, and applies the same technique toIQ1_S.

也就是说,#515#516两个 PR 先在其它量化类型上验证了一套"同一种技术"(same technique),本 PR 把该技术推广到IQ1_S。这一技术路线在仓库 README.md 的 changelog 中有明确呼应:

Much faster CPU prompt processing for all non-interleaved quants. Initial idea in [PR 515] and [PR 531], with many follow up PRs to apply to all quantization types for the 3 supported CPU platforms.

由此可见,IQ1_S属于"非交错(non-interleaved)"量化类型,而 PR 515/516/517 以及后续一系列 PR 的目标,就是为这类非交错量化类型提供更快的 CPU 矩阵乘法路径。需要强调的是,PR 517 本身处于Closed状态,其价值在于把这项优化正式扩展到IQ1_S上,并为 README 中"所有非交错量化类型均获得更快 CPU 提示处理"这一最终成果贡献了关键一环。

极低比特量化双雄:IQ1_S 与 IQ1_S_R4 的内存布局对比

在深入内核之前,先厘清 PR 对比的两个量化类型。根据 examples/quantize/quantize.cpp 中的量化类型注册表:

类型名GGML 类型描述
IQ1_SLLAMA_FTYPE_MOSTLY_IQ1_S(= 24,见 include/llama.h)1.56 bpw 量化
IQ1_S_R4LLAMA_FTYPE_MOSTLY_IQ1_S_R4(= 224,见 include/llama.h)1.5 bpw 量化

两者都处在 1.5 bpw 左右的"极限压缩"区间,但底层块结构完全不同。看 ggml/src/ggml-common.h 中的定义:

typedef struct { ggml_half d; // 1 个 fp16 缩放因子 uint8_t qs[QK_K/8]; // 256/8 = 32 字节量化权重 uint16_t qh[QK_K/32]; // 256/32 = 8 个 uint16 高位辅助位 } block_iq1_s; // 合计 2 + 32 + 16 = 50 字节,对应 256 个元素 typedef struct { uint8_t qs[16]; // 16 字节量化权重 uint16_t qh[4]; // 4 个 uint16 高位辅助位 } block_iq1_s_r4; // 合计 24 字节,对应 32 个元素(4 行交错)

两者的关键差异在 ggml/src/ggml.c 的 GGML 类型表中体现得更加直接:

[GGML_TYPE_IQ1_S] = { .type_name = "iq1_s", .blck_size = QK_K, // 256 元素一块 .type_size = sizeof(block_iq1_s), // 50 字节 .vec_dot = ggml_vec_dot_iq1_s_q8_K, // 与 Q8_K 做点积 ... }, [GGML_TYPE_IQ1_S_R4] = { .type_name = "iq1_s_r4", .blck_size = 32, // 32 元素一块 .type_size = sizeof(block_iq1_s_r4)/4, // 24/4 = 6 字节 .vec_dot = vec_dot_iq1_s_r4_q8_k, // 与 Q8_K128 做点积 ... },

注意IQ1_S采用256 元素大块(QK_K)、单行连续布局,属于"非交错"格式;而IQ1_S_R4采用32 元素小块、4 行交错(row-interleaved)布局,这也是_R4后缀的由来——README 中-rtr选项的描述也印证了这一点:该选项会把留在内存中的张量重打包为"row-interleaved"格式,而IQ1_S_R4正是这种交错布局的典型代表。

PR 中对比的正是这两种布局:IQ1_S_R4作为"已经存在的交错格式加速方案",IQ1_S(主分支的常规实现)与IQ1_S(本 PR 的优化实现)作为被比较对象。

提速内核的源码级原理:iqk_set_kernels_1bit 的三大关键点

IQ1_S提速的核心落地在 IQK(ik 量化内核)模块中。该模块默认不参与编译,需要通过 ggml/src/CMakeLists.txt 中的GGML_IQK_MUL_MAT选项开启:

set (GGML_SOURCES_IQK iqk/iqk_quantize.cpp iqk/iqk_cpu_ops.cpp) if (GGML_IQK_MUL_MAT) add_compile_definitions(GGML_USE_IQK_MULMAT) set(GGML_SOURCES_IQK_MM iqk/iqk_mul_mat.cpp ...) ...

开启后,GGML_USE_IQK_MULMAT宏会激活 ggml/src/iqk/iqk_mul_mat.cpp 中的iqk_mul_matiqk_mul_mat_4diqk_mul_mat_moe等入口(分别面向普通 GEMM、4D 批量 GEMM 与 MoE 专家并行三种场景),并替代默认的vec_dot点积路径——这也是"prompt processing 提速"的机制基础:提示处理本质上是大批量(batch)的矩阵乘法,批量越大,GEMM 内核相对逐向量点积的优势越明显。

IQ1_S专用内核的选择与分发逻辑在 ggml/src/iqk/iqk_gemm_1bit.cpp 的iqk_set_kernels_1bit函数中,值得逐点拆解:

case GGML_TYPE_IQ1_S: if (ne00%QK_K != 0) return false; // 要求 K 维是 256 的倍数 if (actual_typeB == GGML_TYPE_Q8_2_X4) { IQK_SET_MUL_MAT_FUNCTIONS(mul_mat_iq1_s_q8_2_x4, funcs); // 变体 A:B 矩阵用 Q8_2_X4 expected_typeB = GGML_TYPE_Q8_2_X4; } else { IQK_SET_MUL_MAT_FUNCTIONS(mul_mat_iq1_s_q8_K, funcs); // 变体 B:B 矩阵用 Q8_K #ifdef HAVE_FANCY_SIMD func16 = mul_mat_iq1_s_q8_K<16>; // 关键点:16 路 SIMD 专用变体 #endif expected_typeB = GGML_TYPE_Q8_K; } break; case GGML_TYPE_IQ1_S_R4: if (ne00%128 != 0) return false; IQK_SET_MUL_MAT_FUNCTIONS(mul_mat_iq1_s_r4_q8_1, funcs); // R4 走另一套内核 ... break;

关键点一:按 B 矩阵类型分发两条内核路径

IQ1_S权重(A 矩阵)固定后,右侧激活(B 矩阵)可以是Q8_K(每块 256 元素的标准激活量化)或Q8_2_X4(更新的激活量化格式),内核分别选择mul_mat_iq1_s_q8_Kmul_mat_iq1_s_q8_2_x4。这印证了 ggml/src/iqk/iqk_mul_mat.cpp 中的逻辑:对于IQ1_S,当一行中的列数(nrc_y)足够大(≥32)时,B 矩阵会被量化为Q8_K(或其行交错变体Q8_K_R8),以保证 GEMM 的吞吐。

关键点二:HAVE_FANCY_SIMD 下的 16 路内核

func16 = mul_mat_iq1_s_q8_K<16>是模板实例化出的 16 路 SIMD 变体。在支持 AVX-512 / Zen4 等"高级 SIMD"指令集的 CPU 上(ggml/src/CMakeLists.txt 附近对HAVE_FANCY_SIMD的警告信息可佐证其与 Zen4 等平台相关),一次可以并行处理 16 行输出,显著摊薄权重加载与解量化开销。这正是"非交错大块布局"得以提速的关键:256 元素大块让 SIMD 寄存器得以充分复用。

关键点三:与 R4 内核的差异即性能差距来源

IQ1_S_R4走的是mul_mat_iq1_s_r4_q8_1mul_mat_iq1_s_r4_q8_1<16>,但要求 K 维只是 128 的倍数。PR 的核心结论是:在 PR 517 的优化下,非交错的IQ1_S已经追平甚至反超了原本为提速而设计的IQ1_S_R4,且两者的内核路径从此可以各自独立演进。

实测数据:Ryzen-7950X 上 Llama-3.1-8B 的三组 sweep-bench 对比

PR 文档完整给出了三组基准数据,测试对象为Llama-3.1-8B 的IQ1_S量化模型,平台为Ryzen-7950X CPU。三组数据分别对应:主分支上的IQ1_S、主分支上的IQ1_S_R4、以及本 PR 中的IQ1_S。表格列含义遵循 examples/sweep-bench/README.md 的定义:PP为每个 ubatch 的提示词 token 数,TG为每个 ubatch 生成的 token 数,N_KV为当前 KV cache 大小,S_PP为提示处理速度(t/s),S_TG为文本生成速度(t/s)。

IQ1_S,main branch(优化前基线)

PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s
51212803.272156.474.60527.79
5121285123.351152.775.09225.14
51212810243.402150.525.08425.18
51212815363.677139.255.20124.61
51212820483.586142.795.51523.21

IQ1_S_R4,main branch(交错格式对照)

PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s
51212803.101165.104.54328.18
5121285123.166161.744.83626.47
51212810243.309154.755.28224.23
51212815363.348152.925.09325.13
51212820483.447148.555.26524.31

IQ1_S,PR(本 PR 优化后)

PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s
51212801.855275.944.64327.57
5121285121.940263.875.05625.32
51212810242.188234.055.09925.10
51212815362.097244.205.11225.04
51212820482.184234.425.36823.85

数据解读

1. 提示处理(PP)接近翻倍。N_KV=0的空缓存场景为例,S_PP从 156.47 t/s(主分支 IQ1_S)跃升至 275.94 t/s,提升约76%;即使按 PR 文档"nearly 2X"的表述取宽口径(对比最差的 512/2048 窗口:142.79 → 234.42,提升约 64%;对比 1536 窗口:139.25 → 244.20,提升约 75%),其结论依然成立——T_PP(即首 token 延迟)从 3.2~3.7 秒压缩到 1.9~2.2 秒,长提示词的首 token 等待时间大幅缩短。

2. 全面反超IQ1_S_R4优化后的IQ1_S在全部 5 个 KV 窗口下的S_PP(275.94/263.87/234.05/244.20/234.42)都显著高于IQ1_S_R4(165.10/161.74/154.75/152.92/148.55)。也就是说,原本用户可能为了提示处理速度而选择 4 行交错的IQ1_S_R4,现在非交错的IQ1_S反而是更快的选择。

3. 文本生成(TG)几乎不受影响。三组数据的S_TG均在 23~28 t/s 区间内浮动,T_TG也基本一致(PR 与基线相差在 1% 量级)。这与预期一致:token 生成是 GEMV(矩阵-向量乘)主导,向量化收益有限,本次优化几乎全部集中在 GEMM 主导的提示处理阶段。从内核分发逻辑(iqk_set_kernels_1bit只在 matmul 路径生效)也可以推断,优化并不改变逐 token 解码的底层点积路径。

如何复现:用 llama-sweep-bench 跑出同样的数据

PR 文档中的表格来自仓库自带的llama-sweep-bench工具(源码在 examples/sweep-bench/sweep-bench.cpp,构建目标llama-sweep-bench)。该工具的设计目标(见 examples/sweep-bench/README.md)是:对整段上下文按 ubatch 窗口做扫描,在每个窗口内分别测量提示处理与 token 生成性能,从而可视化"性能随上下文长度变化"的曲线,而不是像普通 bench 那样把指标在整个上下文上平均。

复现步骤与 PR 数据完全对应:

# 用 IQ1_S 量化模型运行 sweep-bench ./llama-sweep-bench -c 8704 -ub 512 -m models/Llama-3.1-8B-IQ1_S.gguf
  • -c 8704:上下文总长度(对应 PR 数据中 N_KV 0~2048 之外的更大扫描范围)
  • -ub 512:ubatch 大小,即每次提示处理窗口的 token 数(对应表中PP=512
  • -m:模型文件路径

工具会输出与 PR 文档相同格式的 Markdown 表格。若需要机器可读输出,可追加--output-format jsonl,每行 JSON 包含pptgn_kvt_ppspeed_ppt_tgspeed_tg等字段,便于脚本化对比多次构建的差异(README 中给出了完整 JSONL 示例)。要复现 PR 的完整对比,只需分别用 main 分支与包含 PR 517 变更的构建运行同一模型即可。

配套实践:如何得到 IQ1_S 量化模型

要在实际项目中使用优化后的IQ1_S内核,首先需要一个IQ1_S格式的 GGUF 模型。仓库提供llama-quantize工具(入口见 examples/quantize/quantize.cpp),类型名直接使用IQ1_SIQ1_S_R4

./llama-quantize --imatrix imatrix.dat model-f16.gguf model-IQ1_S.gguf IQ1_S

需要特别留意的是 quantize 工具中的硬性提示(examples/quantize/quantize.cpp):

params.ftype == LLAMA_FTYPE_MOSTLY_IQ1_S || params.ftype == LLAMA_FTYPE_MOSTLY_IQ1_S_R4 || ... fprintf(stderr, "Please do not use IQ1_S, IQ1_M, IQ2_S, IQ2_XXS, IQ2_XS or Q2_K_S quantization without an importance matrix\n");

IQ1_S这类 1.5~1.6 bpw 的极限量化类型**必须配合重要性矩阵(imatrix)**使用,否则精度损失会非常严重。imatrix.dat可由llama-imatrix工具基于代表性语料生成(仓库提供get-wikitext-2.shget-pg.sh等语料脚本,见 scripts/ 目录)。此外,在 CPU 侧,IQ1_S的量化权重(A 矩阵)通常需要配合 Q8 量化的激活(B 矩阵)做矩阵乘法,即iqk_set_kernels_1bitexpected_typeB = GGML_TYPE_Q8_K的约束——这也解释了为何 README 中关于-rtr的警告:混合 CPU/GPU 推理时若把IQ1_S权重行交错化(R4 化),反而可能丢失 CPU 侧最优 GEMM 路径。

结语:一次"非交错极低比特"的胜利

PR 517 的实质,是把 PR 515/516 开创的 IQK GEMM 加速技术推广到IQ1_S:通过iqk_set_kernels_1bit为 256 元素大块的非交错布局注册专用矩阵乘法内核,并在支持高级 SIMD 的 CPU 上启用 16 路并行变体,最终在 Ryzen-7950X 上把 Llama-3.1-8BIQ1_S的提示处理吞吐从约 150 t/s 级别拉升至 230~275 t/s 级别,同时不牺牲 token 生成速度,并反超了此前的交错格式方案IQ1_S_R4。正如 README changelog 所总结的,这条技术路线后续被推广到所有非交错量化类型与三大 CPU 平台,成为 ik_llama.cpp 在 CPU 提示处理性能上的一项标志性优化。对于在 CPU 上部署 1.5 bpw 级极限压缩模型的场景,本文涉及的IQ1_S+ IQK GEMM 组合是目前仓库内可直接使用的高吞吐方案。

  • 人工智能
  • 大模型
  • 推理引擎
  • 本地部署
  • 模型量化
  • 模型优化

【免费下载链接】ik_llama.cpp

llama.cpp fork with additional SOTA quants and improved performance

项目地址:https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
点击查看免费下载

相关推荐

上一篇:Contrastive Predictive Coding-PyTorch 项目教程
下一篇:Rufus制作U盘启动盘教程:3次点击把ISO变系统安装盘

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

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

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

立即咨询