尧图精选

ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速:PR 186 源码级解析与实测数据

🕒 发布时间:2026/9/19 7:34:40 📁 来源:尧图网络
ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速PR #186 源码级解析与实测数据【免费下载链接】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 #186iq1_s_r4: slightly faster NEON gemm/gemv为线索深入解析 1 比特量化格式 IQ1_S_R4 的数据布局、量化/反量化流程以及它在 ARM NEONApple Silicon上的矩阵乘GEMM与矩阵向量乘GEVM优化实现。读者将掌握IQ1_S_R4 与 IQ1_S 的格式差异、iqk子库中mul_mat_iq1_s_r4_q8_1的内核级优化思路、PR #186 在 M2-Max 上的实测吞吐提升数据以及如何理解这类 R4四行打包量化在解码token generation与预填充prompt processing阶段的性能影响。背景ik_llama.cpp 与 1 比特量化家族ik_llama.cpp 是 llama.cpp 的一个 fork定位为“附加 SOTA 量化格式并提升性能”additional SOTA quants and improved performance。在极端低比特量化方向该仓库维护了一系列 IQ“I”指 integer即整数内核量化格式其中IQ1_S1.56–1.75 bpw 级别是面向极小模型体积的 1 比特量化格式。为了在保持量化精度的同时提升 CPU 上的计算吞吐仓库引入了R4row-4四行打包变体例如 IQ1_S_R4、IQ1_M_R4、IQ3_S_R4 等。PR #186 正是针对IQ1_S_R4的 ARM NEON 矩阵乘内核做的一次“slightly faster”微优化官方实测在 DeepSeek-Litedeepseek2 16BIQ1_S_R4 量化上带来约 4.6%12.4% 的吞吐提升。本文后续章节将逐一拆解其底层原理与数据。IQ1_S_R4 的块布局从 block 定义看设计意图IQ1_S_R4 的核心数据结构定义在 ggml/src/ggml-common.h 中typedef struct { ggml_half d; uint8_t qs[QK_K/8]; uint16_t qh[QK_K/32]; } block_iq1_s; static_assert(sizeof(block_iq1_s) sizeof(ggml_half) QK_K/8 QK_K/16, wrong iq1_s block size/padding); typedef struct { uint8_t qs[16]; uint16_t qh[4]; } block_iq1_s_r4; static_assert(sizeof(block_iq1_s_r4) 24, wrong iq1_s_r4 block size/padding);关键差异解读IQ1_S上每个QK_K默认 256权重为一组包含一个 fp16 缩放因子d、QK_K/8字节的低位索引qs和QK_K/32个 uint16 的高位索引qh整体 1.5625 bpw。IQ1_S_R4下不再为每个块单独存缩放因子而是将 4 行打包成一个 24 字节的块qs[16]qh[4]每个qh为 uint16即 4×16 bit。d缩放因子被移出块外由上层计算逻辑按 4 行一组单独处理见下文量化函数。从量化实现看ggml/src/iqk/iqk_quantize.cpp 中quantize_iq1_s_r4以nrows4、n_per_rowk/4的方式工作缩放因子单独写入数据流前端dptr 4处为 4 个 half 精度缩放size_t quantize_iq1_s_r4(const float * src, void * dst, int64_t nrows, int64_t n_per_row, const float * imatrix, const struct quantize_user_data * use_data) { // ... auto y (block_iq1_s_r4 *)(dptr 4); // ... }这种“四行共享缩放 紧凑位打包”的设计使 R4 变体在相同 bpw 下能减少缩放因子的内存带宽开销并让向量化内核一次加载 4 行的数据块做批处理详见第 4 节。支持 IQ1_S_R4 的相关源码位置环节文件说明块布局定义ggml/src/ggml-common.hblock_iq1_s_r424 字节量化/反量化ggml/src/iqk/iqk_quantize.cppquantize_row_iq1_s_r4_ref、quantize_iq1_s_r4点积参考实现ggml/src/iqk/iqk_quantize.hvec_dot_iq1_s_r4_q8_k等NEON/x86 矩阵乘ggml/src/iqk/iqk_gemm_1bit.cppmul_mat_iq1_s_r4_q8_1内核模型转换src/llama-quantize.cpp{ GGML_TYPE_IQ1_S_R4, { GGML_TYPE_IQ1_S, 4} }GGUF 类型注册ggml/src/ggml-backend.cppGGML_TYPE_IQ1_S_R4的算子分发PR #186 做了什么NEON 内核层面的微优化PR #186 的核心改动位于 ggml/src/iqk/iqk_gemm_1bit.cpp 中的mul_mat_iq1_s_r4_q8_1。这是 IQ1_S_R4 权重与Q8_1激活做矩阵乘GEMM与矩阵向量乘GEVM的统一内核模板函数模板参数nrc_y决定一次处理的目标行数即输出通道数。template int nrc_y static void mul_mat_iq1_s_r4_q8_1(int n, const void * vx, size_t bx, const DataInfo info, int nrc_x) { GGML_ASSERT(nrc_x%4 0); Q8nrc_y, block_q8_K128 q8(info); int nb n / 32; GGML_ASSERT(nb%4 0); __m256i qx[4]; __m256 acc[nrc_y] {}; auto m1 _mm256_set1_epi16(1); auto ms _mm_set1_epi16(-32768); float d8[4*nrc_y]; // ... }从源码结构看该内核的优化要点可归纳为以下几点四行并行nrc_x%4 0每次迭代同时处理 4 行权重ix 44 个__m256i qx[4]向量分别承载 4 行的解码结果最大化 SIMD 寄存器复用减少权重数据的重复加载。符号位与缩放因子的合并解码IQ1_S 的索引由低位索引qs与高位符号/索引qh共同构成。内核先通过_mm_srli_epi16(sas, 12)提取 3 bit 缩放索引再构造 ±1 符号并乘上缩放signs _mm_mullo_epi16(signs, scales4)随后用查表iq1s_grid_us[...]将 16 个索引一次性映射为网格点_mm256_set_epi64x组合把 1 比特权重展开成便于整数点积的形态。整数量化点积dpbusd/maddubs在支持HAVE_FANCY_SIMDAVX-VNNI 等的平台用_mm256_dpbusd_epi32完成 8-bit 乘累加否则回退到_mm256_maddubs_epi16_mm256_madd_epi16的两级归约。这与 x86 端对mul_mat_iq1_s_r4_q8_1的另一个变体mul_mat_iq1_s_r4_q8_1_1见 iqk_gemm_1bit.cpp保持一致只是指令集分支不同。外层累加与延迟写回4 行的部分和先累积在__m256 acc[nrc_y]寄存器中最后统一info.store(...)写回并把 1/8 的常数归约hsum/_mm_mul_ps(d1, sumf)合并到收尾阶段减少写内存次数。需要说明PR #186 标题中的“NEON gemm/gemv”指该优化落地于ggml/src/iqk的 CPU 内核路径同时覆盖 x86 与 NEON 指令集分支实测环境为 Apple M2-Max。从当前仓库源码看该内核同时保留了 AVX 与 NEON 分支的编译路径HAVE_FANCY_SIMD等宏决定使用哪套指令序列。内核的注册方式见 iqk_gemm_1bit.cppIQK_SET_MUL_MAT_FUNCTIONS(mul_mat_iq1_s_r4_q8_1, funcs); func16 mul_mat_iq1_s_r4_q8_116;这表示该函数会被绑定为 IQ1_S_R4 在 iqk 路径下的 GEMM/GEVM 算子供 ggml/src/iqk/iqk_mul_mat.cpp 的统一调度层按数据类型分派。实测性能PR #186 在 DeepSeek-Lite 上的提升PR #186 的描述中给出了官方在M2-Max CPU、DeepSeek-Litedeepseek2 16BIQ1_S_R4 量化上的对比测试列于下表。tg128表示生成token generation测试中一次处理 128 token 的批大小pp512表示预填充prompt processing测试中一次处理 512 token 的批大小。t/s为吞吐tokens per secondmain为合并基线PR为应用本 PR 后的版本。模型threads测试t/s (main)t/s (PR)加速比deepseek2 16B IQ1_S_R42tg12822.76 ± 0.1524.07 ± 0.191.058deepseek2 16B IQ1_S_R44tg12837.83 ± 0.0039.58 ± 0.021.046deepseek2 16B IQ1_S_R48tg12862.01 ± 0.0265.26 ± 0.821.052deepseek2 16B IQ1_S_R48pp512251.97 ± 0.09283.20 ± 0.541.124从数据可以总结出三个规律均以该 PR 实测为前提不代表其他硬件/模型结论解码tg提升稳定在 4.6%5.8%2/4/8 线程下的加速比分别为 1.058 / 1.046 / 1.052与 PR 标题“slightly faster”的描述一致。解码是内存带宽受限场景微内核优化带来的寄存器级收益被换算为 5% 左右的吞吐提升。预填充pp512提升显著达 12.4%251.97 → 283.20 t/s。预填充是计算密集场景GEMM 内核的 SIMD 效率直接决定吞吐因此优化收益被放大。多线程扩展性良好2→8 线程时基线 22.76 → 62.01 t/s约 2.7×PR 版本 24.07 → 65.26 t/s说明优化没有引入串行瓶颈。这类 “tg 微升、pp 大增” 的分布是低比特量化 CPU 内核优化的典型特征也解释了为什么仓库后续围绕 R4 系列量化IQ1_M_R4、IQ3_S_R4、IQ4_KSS 等持续投入矩阵乘内核优化。为什么关注 R4从算子注册到模型转换的完整链路IQ1_S_R4 并非只存在于内核层它在量化与模型转换链路中被完整支持在 src/llama-quantize.cpp 中IQ1_S_R4被定义为IQ1_S的 R4 变体{ GGML_TYPE_IQ1_S_R4, { GGML_TYPE_IQ1_S, 4 } }表示它是把 4 个IQ1_S块合并打包的格式。同一文件src/llama-quantize.cpp中记录了格式转换关系LLAMA_FTYPE_MOSTLY_IQ1_S → LLAMA_FTYPE_MOSTLY_IQ1_S_R4即常规 IQ1_S 模型可进一步 repack 为 R4 格式。GGUF 文件类型LLAMA_FTYPE_MOSTLY_IQ1_S_R4在 src/llama-quantize.cpp 中映射到GGML_TYPE_IQ1_S_R4最终由 ggml 后端的类型表分派到 iqk 内核。因此一个完整的 IQ1_S_R4 使用链路是HF 模型 → llama-quantize 量化为 IQ1_S_R4或从 IQ1_S repack → GGUF 加载llama-model-loader → ggml 类型分派GGML_TYPE_IQ1_S_R4 → iqk 调度层 → mul_mat_iq1_s_r4_q8_1NEON/AVX 内核仓库内还保留了独立的 CPU 参考实现vec_dot_iq1_s_r4_q8_k声明于 ggml/src/iqk/iqk_quantize.h用于非 SIMD 平台或正确性对照GPU 端CUDA/MMQ、MMVQ 模板见 ggml/src/ggml-cuda/template-instances/mmq-instance-iq1_s_r4.cu也有对应实现但 PR #186 的优化范围明确限定在 CPU 的 NEON gemm/gemv 路径。如何在当前仓库中复现与验证要在自己的机器上复现 PR #186 类似的基准可按以下步骤操作以 Linux/macOS CMake 为例前提是已安装对应编译工具链构建 ik_llama.cpp在仓库根目录cmake -B build -DGGML_IQK_MULMATON -DCMAKE_BUILD_TYPERelease cmake --build build --config Release -jGGML_IQK_MULMAT决定是否启用 iqk 矩阵乘路径若关闭将回退到常规点积实现无法体现本文所述内核收益。Apple Silicon 默认启用 NEON 分支x86 平台走 AVX/AVX-VNNI 分支。准备 IQ1_S_R4 模型使用llama-quantize将目标模型量化为IQ1_S_R4或对已有IQ1_S模型执行 R4 repack对应 src/llama-quantize.cpp 的转换关系。./build/bin/llama-quantize 原始模型.gguf 输出模型-IQ1_S_R4.gguf IQ1_S_R4跑基准使用仓库自带的llama-benchexamples/llama-bench分别测试tg128与pp512场景./build/bin/llama-bench -m 输出模型-IQ1_S_R4.gguf -t 8 -b 512 -n 128将得到与 PR 描述同源的t/s指标若与基线版本对比如先测合并基线再测含 PR 的构建即可复现 4.6%12.4% 量级的加速比分布。注意 PR #186 状态为已关闭Closed其优化应已并入主分支当前仓库源码ggml/src/iqk/iqk_gemm_1bit.cpp中的mul_mat_iq1_s_r4_q8_1即为其最终形态。小结PR #186 是 ik_llama.cpp 在“1 比特量化 × CPU 内核优化”方向上的一次典型微迭代通过重构 IQ1_S_R4 的 SIMD 解码与整数量化点积路径在 M2-Max 上为 DeepSeek-Lite 16B 带来解码约 5%、预填充约 12% 的吞吐提升。从block_iq1_s_r4的紧凑布局到mul_mat_iq1_s_r4_q8_1的四行并行与查表解码再到llama-quantize的格式转换链路整个仓库为这种“低 bpw 高吞吐”的目标提供了完整闭环。对于关注极端低比特 CPU 推理的开发者本文梳理的源码路径ggml-common.h、iqk_quantize.cpp、iqk_gemm_1bit.cpp可作为继续深入 iqk 子库的起点。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →