ik_llama.cpp R4 量化矩阵乘法优化解析:superblock 内整数累加器如何在 Zen4 上提速推理
2026/9/19 16:29:34 网站建设 项目流程

ik_llama.cpp R4 量化矩阵乘法优化解析:superblock 内整数累加器如何在 Zen4 上提速推理

【免费下载链接】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 #139 的核心优化:在 R4(4 行交错)量化的矩阵乘法中,于 superblock 内部改用整数累加器完成点积,以规避浮点累加的转换开销。文章将完整还原该优化的动机(Intel 指令延迟顾虑 vs. ARM_NEON 的先行验证)、Zen4(Ryzen-7950X)上的 PP-512 / TG-128 实测提速数据,并结合ggml/src/iqk/下源码中的_mm256_dpbusd_epi32_mm512_dpwssd_epi32等内建指令,说明整数累加路径在当前的实现形态。读完你既能掌握 R4 量化与-rtr运行期重打包的关系,也能理解"看似高延迟的指令组合反而更快"这一底层结论的来源与适用边界。

背景:R4 量化与行交错布局

R4 后缀(如Q2_K_R4Q3_K_R4Q6_K_R4)表示4 行交错(row-interleaved)的量化布局变体,是 ik_llama.cpp 在标准 k-quants(Q2_KQ3_KQ6_K等)之上派生的专用格式。其目标是在 CPU 上把矩阵乘法的内存访问模式优化为更适合 SIMD 的形态:多个输出行共享同一块量化数据,从而减少数据搬运、提升向量化效率。

这种交错变体与运行期重打包开关-rtr--run-time-repack)直接相关。根据 docs/parameters.md 的说明,启用-rtr后,留在内存(RAM)中的张量会被重打包为对应的交错变体(如果可用),在某些系统上可能提升性能。重打包映射关系在 ggml/src/iqk/iqk_quantize.cpp 中定义,例如:

  • Q2_KQ2_K_R4(4 行交错)
  • Q3_KQ3_K_R4(4 行交错)
  • Q6_KQ6_K_R4(4 行交错)

需要特别注意的是,R4/R8 等交错变体目前没有 CUDA 实现,因此一旦使用这些类型,相关矩阵乘法会始终在 CPU 上执行(详见 README.md 中的相关说明)。本文讨论的整数累加器优化正是作用于这些 CPU 侧的 R4 矩阵乘法路径。

PR #139 的核心改动:superblock 内改用整数累加器

PR #139(对应仓库文档 github-data/pull_requests/139 - Faster R4 quants on Zen4.md)的核心只有一句话:在 superblock 内部的点积计算中改用整数累加器

在 k-quants 类量化格式中,一个 256 元素的 superblock 被划分为若干子块(subblock),每个子块拥有自己的 scale/dmin 元数据。量化矩阵乘法通常分两步:

  1. 在子块内部,将量化权重与激活的 8 位整数量化值相乘累加,得到整数部分和;
  2. 将各子块的整数部分和乘以对应 scale 并求和,得到最终的浮点结果。

PR #139 的改动发生在第 1 步与第 2 步的衔接处:把"子块内乘累加后立即转浮点、再在浮点域求和"的旧路径,改为在 superblock 范围内全程使用整数累加器(int32 累加),直到整个 superblock 的点积完成后,才一次性与 scale 结合并转回浮点。这样既减少了整型→浮点的反复转换,也让 SIMD 流水线中的整数运算链更紧凑。

为什么最初没有采用:_mm256_mullo_epi32的高延迟顾虑

作者在 PR 描述中明确交代了该方案最初被放弃的原因:根据 Intel Intrinsics 官方参考文档的说明,_mm256_mullo_epi32()(256 位整数 32 位乘法低半部)指令的延迟(latency)极高。在 AVX2 的经典指令时序中,mullo_epi32通常是多个周期的高延迟指令,远高于浮点乘加vfmadd系列。基于这一常识判断,作者最初认为在点积热路径中引入高延迟整数乘法得不偿失,因此没有在 x86 侧采用整数累加方案。

转折点:ARM_NEON 上的先行验证

真正的转折来自 ARM 平台的经验。在此 PR 之前的优化(编号 #135)中,作者在ARM_NEON上启用了整数点积累加,结果获得了显著的性能提升——NEON 的vdotq_s32等内建点积指令天然以整数累加为核心,收益明显。这促使作者决定在 x86 侧也"冒险"尝试一次:尽管_mm256_mullo_epi32延迟高,但延迟高不等于吞吐低——在矩阵乘法这种指令级并行的场景中,多个独立累加链可以交错执行,从而掩盖单条指令的延迟。

结论是:尽管整数乘法延迟高,实测反而更快。这一结果也印证了一个通用优化原则:SIMD 热点优化中,指令延迟(latency)与吞吐(throughput)必须分开评估,单个指令的糟糕延迟并不必然导致整体性能下降。

实测数据:Zen4 上 LLaMA-3.1-8B 的提速

PR 提供了在Zen4(Ryzen-7950X CPU)上、以LLaMA-3.1-8B为基准模型的完整测量。测试覆盖两类任务:

  • PP-512:Prompt Processing,单次处理 512 token 的预填充吞吐;
  • TG-128:Token Generation,生成 128 token 的解码吞吐(对应矩阵-向量乘法路径)。

下表为原 PR 数据(t/s (main)为改动前主分支,t/s (PR)为 PR 分支,Speedup 为加速比):

QuantThreadsTaskt/s (main)t/s (PR)Speedup
Q2_K_R416pp512256.19 ± 0.26272.69 ± 0.131.064
Q2_K_R41tg1289.08 ± 0.129.95 ± 0.001.096
Q2_K_R42tg12816.40 ± 0.0017.44 ± 0.011.063
Q2_K_R44tg12820.72 ± 0.1220.97 ± 0.081.012
Q3_K_R416pp512236.77 ± 0.35255.84 ± 0.201.081
Q3_K_R41tg1286.78 ± 0.007.16 ± 0.071.056
Q3_K_R42tg12812.46 ± 0.0013.00 ± 0.011.043
Q3_K_R44tg12817.02 ± 0.0917.20 ± 0.241.012
Q4_K_R416pp512262.40 ± 0.28268.09 ± 0.121.022
IQ4_XS_R416pp512256.80 ± 0.35271.95 ± 0.391.059
Q5_K_R416pp512248.30 ± 0.29256.68 ± 0.311.034
Q6_K_R416pp512243.25 ± 0.31261.33 ± 0.381.074
Q6_K_R41tg1287.94 ± 0.008.34 ± 0.001.050
Q6_K_R42tg12810.38 ± 0.0010.38 ± 0.001.000

从中可以提炼出几个值得关注的规律:

  1. 预填充(PP)全面受益:所有被测 R4 类型的 pp512 均获得正加速,其中Q3_K_R4(1.081)、Q6_K_R4(1.074)、Q2_K_R4(1.064)收益最大,Q4_K_R4提升最弱(1.022)。
  2. 低线程数下解码收益最明显Q2_K_R4单线程 tg128 加速比高达1.096,但随着线程数增加到 4,加速比回落到 1.012。这说明整数累加省下的开销在带宽受限/并行度不足的解码路径上贡献更大。
  3. 存在收益为零的情形Q6_K_R4双线程 tg128 的 t/s 完全一致(10.38),Speedup 为 1.000,说明该场景下优化未能转化为吞吐提升。

适用边界:并非所有 R4 类型都走这条路径

PR 描述中明确指出了改动的适用限制:对于Q4_K_R4Q5_K_R4IQ4_XS_R4,其矩阵-向量乘法(TG 路径)由另一套不同的实现完成,整数累加器改动不适用于该路径,因此原 PR 没有给出这三种类型的 TG 数据。这解释了表格中这三行只有 pp512 结果、没有 tg128 结果的原因。

在源码层面,这一分工同样可以印证。ggml/src/iqk/iqk_gemm_kquants.cpp 中的iqk_set_kernels_kquants()为不同量化类型注册了不同的乘法内核,例如Q2_K_R4mul_mat_q2_k_r4_q8_kQ3_K_R4mul_mat_q3_k_r4_q8_k,而Q4_K_R4Q5_K_R4需要Q8_K32作为右侧激活量化类型(expected_type_B判断),暗示其数据布局与计算路径与其他 R4 类型不同。

源码证据:整数累加指令在 R4 内核中的实际形态

虽然 PR #139 当时的代码已经演进,但当前仓库 ggml/src/iqk/iqk_gemm_kquants.cpp 中依然可以观察到"整数累加"思路的延续与深化,多个内核使用 AVX-512 / AVX-512 VNNI 的整数点积指令族:

  • _mm512_dpbusd_epi32:无符号字节点积并累加为 int32,用于把 8 位量化权重与 8 位激活的逐元素乘积直接累加进 32 位累加器(见文件第 183–186、404–407、502–505 行);
  • _mm512_dpwssd_epi32:带符号 16 位字点积并累加为 int32,配合_mm512_packs_epi32将字节点积结果打包后与 scale 向量完成最终加权累加(见第 187–188 行等);
  • AVX2 路径同样使用_mm256_dpbusd_epi32(见第 833–836 行)。

这种"子块内dpbusd字节点积 →pack打包 →dpwssd加权累加"的模式,正是 PR #139 所倡导的"superblock 内整数累加、最后一次性换算浮点"思想的现代表现形式:整数累加链覆盖到整个 superblock,仅在点积完成时才乘以 scale 并转浮点。与之配套,ggml/src/iqk/iqk_common.h 中也能看到 AVX2 的_mm256_maddubs_epi16+_mm256_madd_epi16组合,实现相同的整数乘累加语义。

如何在当前仓库复现与验证

若要在类似 Zen4 的 x86 CPU 上复现这类 R4 量化的 CPU 推理性能,推荐流程如下:

  1. 确认 R4 量化可用:仓库的量化工具支持将标准 k-quants 转为 R4 变体。量化类型注册表位于 ggml/src/iqk/iqk_quantize.cpp(如repack_q2_kQ2_K_R4等),也支持IQ2_K_R4IQ3_K_R4IQ4_KS_R4等 i-quants 变体。
  2. 使用运行期重打包:通过-rtr--run-time-repack)参数让模型加载时自动把权重重打包为交错格式(参考 docs/parameters.md 的参数说明)。注意混合 CPU/GPU 推理场景下,R4 无 CUDA 实现意味着相关张量的乘法始终走 CPU,这一点在 README.md 中有明确警告。
  3. llama-bench测量 PP / TGexamples/llama-bench/提供标准化的预填充(pp)与生成(tg)吞吐基准,可按 PR 中的方法分别测pp512tg128,并与main分支或不同线程数(-t 1/2/4/16)交叉对比,即可复现 1.0x–1.1x 量级的加速区间。

总结

PR #139 是一个典型的"反直觉但正确"的 SIMD 优化案例:作者基于 Intel 指令参考中对_mm256_mullo_epi32高延迟的记载而推迟了方案,又在 ARM_NEON 上看到整数累加的显著收益后回到 x86 验证,最终确认在 superblock 内使用整数累加器比浮点累加更快,即便整数乘法指令延迟更高。实测在 Zen4 上为 LLaMA-3.1-8B 的 R4 量化带来约 2%–10% 的 PP 提速,以及单线程解码最高约 9.6% 的提速;同时,Q4_K_R4/Q5_K_R4/IQ4_XS_R4因使用独立的矩阵-向量实现而未被该改动覆盖,构成了明确的适用边界。当前仓库的 k-quants 内核中dpbusd/dpwssd整数点积指令族的广泛使用,也表明这条优化路线此后被持续继承与扩展。

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

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

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

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

立即咨询