- 人工智能
- 大模型
- 推理引擎
- 本地部署
- 模型量化
- 模型优化
【免费下载链接】ik_llama.cpp
llama.cpp fork with additional SOTA quants and improved performance
本文基于 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_S与IQ1_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 to
IQ1_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_S | LLAMA_FTYPE_MOSTLY_IQ1_S(= 24,见 include/llama.h) | 1.56 bpw 量化 |
IQ1_S_R4 | LLAMA_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_mat、iqk_mul_mat_4d、iqk_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_K与mul_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_1与mul_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(优化前基线)
| PP | TG | N_KV | T_PP s | S_PP t/s | T_TG s | S_TG t/s |
|---|---|---|---|---|---|---|
| 512 | 128 | 0 | 3.272 | 156.47 | 4.605 | 27.79 |
| 512 | 128 | 512 | 3.351 | 152.77 | 5.092 | 25.14 |
| 512 | 128 | 1024 | 3.402 | 150.52 | 5.084 | 25.18 |
| 512 | 128 | 1536 | 3.677 | 139.25 | 5.201 | 24.61 |
| 512 | 128 | 2048 | 3.586 | 142.79 | 5.515 | 23.21 |
IQ1_S_R4,main branch(交错格式对照)
| PP | TG | N_KV | T_PP s | S_PP t/s | T_TG s | S_TG t/s |
|---|---|---|---|---|---|---|
| 512 | 128 | 0 | 3.101 | 165.10 | 4.543 | 28.18 |
| 512 | 128 | 512 | 3.166 | 161.74 | 4.836 | 26.47 |
| 512 | 128 | 1024 | 3.309 | 154.75 | 5.282 | 24.23 |
| 512 | 128 | 1536 | 3.348 | 152.92 | 5.093 | 25.13 |
| 512 | 128 | 2048 | 3.447 | 148.55 | 5.265 | 24.31 |
IQ1_S,PR(本 PR 优化后)
| PP | TG | N_KV | T_PP s | S_PP t/s | T_TG s | S_TG t/s |
|---|---|---|---|---|---|---|
| 512 | 128 | 0 | 1.855 | 275.94 | 4.643 | 27.57 |
| 512 | 128 | 512 | 1.940 | 263.87 | 5.056 | 25.32 |
| 512 | 128 | 1024 | 2.188 | 234.05 | 5.099 | 25.10 |
| 512 | 128 | 1536 | 2.097 | 244.20 | 5.112 | 25.04 |
| 512 | 128 | 2048 | 2.184 | 234.42 | 5.368 | 23.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 包含pp、tg、n_kv、t_pp、speed_pp、t_tg、speed_tg等字段,便于脚本化对比多次构建的差异(README 中给出了完整 JSONL 示例)。要复现 PR 的完整对比,只需分别用 main 分支与包含 PR 517 变更的构建运行同一模型即可。
配套实践:如何得到 IQ1_S 量化模型
要在实际项目中使用优化后的IQ1_S内核,首先需要一个IQ1_S格式的 GGUF 模型。仓库提供llama-quantize工具(入口见 examples/quantize/quantize.cpp),类型名直接使用IQ1_S或IQ1_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.sh、get-pg.sh等语料脚本,见 scripts/ 目录)。此外,在 CPU 侧,IQ1_S的量化权重(A 矩阵)通常需要配合 Q8 量化的激活(B 矩阵)做矩阵乘法,即iqk_set_kernels_1bit中expected_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
相关推荐
ik_llama.cpp Q4_K_R4 量化详解:行交错(Row-Interleaved)布局如何将 CPU 矩阵乘法提速 1.6 倍
ik_llama.cpp Q4_K_R4 量化详解:行交错(Row Interleaved)布局如何将 CPU 矩阵乘法提速 1.6 倍 导读 本文以 ik_l
人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp 的 ARM_NEON R4 量化矩阵乘法优化:模板化改造与 ~7% Prompt 处理加速解析
ik_llama.cpp 的 ARM_NEON R4 量化矩阵乘法优化:模板化改造与 ~7% Prompt 处理加速解析 本篇指南围绕 ik_llama.cpp
人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp R4 量化矩阵乘法优化解析:superblock 内整数累加器如何在 Zen4 上提速推理
ik_llama.cpp R4 量化矩阵乘法优化解析:superblock 内整数累加器如何在 Zen4 上提速推理 导读 本文剖析 ik_llama.cpp
人工智能大模型推理引擎本地部署模型量化模型优化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考