多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ik_llama.cpp Trellis 量化 CPU 提速:PR 482 的 dequantizing GEMM 方案全解析

ik_llama.cpp Trellis 量化 CPU 提速:PR 482 的 dequantizing GEMM 方案全解析 人工智能大模型推理引擎本地部署模型量化模型优化【免费下载链接】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 #482Trellis quants: faster CPU prompt processing展开深入讲解 trellis 量化类型IQ2_KT / IQ3_KT / IQ4_KT在 CPU 上 prompt processingPP性能差的根因以及作者如何通过dequantizing GEMM反量化 GEMM方案将反量化的高成本摊薄到整个右矩阵上从而在不依赖 BLAS 的情况下实现 2~3 倍的 PP 提速。读完本文你将理解 trellis 量化在 x86AVX2与 ARMNEON两条路径上的内核设计差异、内核调度逻辑以及这套方案与 BLAS 的取舍关系并能在当前仓库中找到每一处对应实现。1. 问题背景为什么 Trellis 量化在 CPU 上很慢Trellis 量化是 ik_llama.cpp 引入的一类新型量化格式家族成员包括IQ1_KT、IQ2_KT、IQ3_KT、IQ4_KT在 README.md 中被归入 Trellis quants 小节。这类量化基于一种新颖的integer-base trellis整数基网格码编码其核心思想是相邻的权重值在网格码中共享约束关系因此可以用极少的比特位表示权重同时借助网格结构的纠错特性获得比同比特数传统量化更好的精度。PR #482 明确指出一个关键痛点The trellis quantsIQ2_KT, IQ3_KT, IQ4_KTare very slow on the CPU. On the main branch using BLAS results in a better prompt processing performance. But BLAS is slower for basically all other data types, so thats not a good idea.也就是说IQ2_KT / IQ3_KT / IQ4_KT在 CPU 上做 prompt processing 时非常慢主分支main branch如果启用 BLAS如 Intel MKLPP 性能会更好但 BLAS 对其他几乎所有数据类型都更慢所以不能简单地把 BLAS 作为通用解决方案——这就是 PR #482 要解决的矛盾。1.1 慢的根源反量化dequantization代价极高trellis 量化的权重不是直接存储的数值而是网格码状态。要从存储的码字还原出真实权重必须运行一个 trellis 状态推进过程由 PRNG 风格的递推生成逐个生成权重值。这一过程的计算量远高于传统量化类型如Q4_0、IQ4_KS的简单查表反量化。从 iqk_gemm_ktquants.cpp 的源码可以看到反量化的本质。例如trellis_next使用一组固定的线性同余递推参数inline uint32_t trellis_next(uint32_t val) { constexpr uint32_t ka 89226354; constexpr uint32_t kb 64248484; constexpr uint32_t kmask 0x8fff8fff; constexpr uint32_t km32 0x3b603b60; val val*ka kb; return (val kmask) ^ km32; }x86 侧还专门实现了Trellis1 / Trellis2 / Trellis3三个结构体用 AVX2 的_mm256_mullo_epi32一次并行推进 8 个状态next8再用_mm256_cvtph_ps把半精度值转成浮点累加trellis_gen8。即便如此每个块都要现算权重 的反量化开销仍然远高于普通量化类型。1.2 传统内核模板的局限最多复用 8 个右矩阵列在 PR #482 之前trellis 量化走的是标准内核模板路径。作者在 PR 描述中解释了瓶颈...the standard kernel templates that allow up to 8 right matrix columns.即标准模板在乘一个权重块时最多同时处理右侧矩阵的 8 列。这意味着极高的反量化成本只能被最多 8 列的数据分摊反量化开销无法被有效摊销最终表现为 PP 吞吐率低下。2. 核心方案dequantizing GEMM反量化 GEMMPR #482 的解法非常直接而巧妙This PR improves prompt processing speed of the trellis quants by adding dequantizing GEMM. Basically, blocks of trellis quantized weights are converted tofp32(AVX2) orfp16(ARM) on-the-fly, and then thefp32/fp16GEMM kernels are used to multiply the block with the entire right matrix.拆解一下这个方案的两步边反量化把一块 trellis 量化权重block在寄存器中实时转换成fp32x86/AVX2 路径或fp16ARM/NEON 路径浮点值整矩阵复用用现成的fp32/fp16GEMM 内核拿这块反量化后的权重去乘整个右矩阵全部列而非标准模板限制的 8 列。这样一来一次反量化的高成本就被整个右矩阵的所有列共同分摊摊销效率比标准模板高出几个数量级反量化开销被彻底摊薄。2.1 双平台分支x86 用 fp32ARM 用 fp16同一份源码里根据架构分成两条独立实现见 iqk_gemm_ktquants.cpp 与#else // !__x86_64__分支平台反量化目标类型代表内核指令集x86_64fp32mul_mat_iq2_kt_F32_T等*_F32_T系列AVX2__x86_64__ARMfp16mul_mat_iq2_kt_F16_T等*_F16_T系列NEON fp16float16_tx86 的mul_mat_iq2_kt_F32_Tiqk_gemm_ktquants.cpp展示了完整流程template int nrc_y void mul_mat_iq2_kt_F32_T(int n, const void * vx, size_t bx, const DataInfo info, int nrc_x) { assert(n%QK_K 0); const int nb n/QK_K; Trellis1 trellis; // ... 加载 iq4k 查找表与 scale 位 for (int ix 0; ix nrc_x; ix) { const float d *dptr * 31.75f * 1.05f; // 块级 scale for (int i 0; i nb; i) { // 遍历所有权重块 // 解出 8 个 scale查 iq4k_values 表 // 用 trellis.next8 一次生成 8 个权重AVX2 反量化 auto xval1 _mm256_mul_ps(scale1, trellis_gen8(trellis.next8(ql[...]4096))); // 与右矩阵的整行做 FMA 乘加不再是 8 列限制 accd[0] _mm256_fmadd_ps(_mm256_load_ps(y[0] ...), xval1, accd[0]); } // 乘回全局 scale d水平求和后写回 __m256 res _mm256_mul_ps(_mm256_set1_ps(d), _mm256_add_ps(accd[0], accd[1])); info.store(ix, 0, hsum_float_8(res)); } }ARM 侧mul_mat_iq2_kt_F16_Tiqk_gemm_ktquants.cpp与 x86 版本结构一致只是把__m256/_mm256_fmadd_ps换成float16x8_t/vfmaq_f16用 NEON 的 fp16 FMA 指令完成同样的反量化 整列复用计算。作者特别给出了两个平台的实测结论Zen4/AVX2 CPU该方案的 PP 性能优于 BLAS含 Intel MKLM2 MaxARMPP 性能约为 BLAS 的 80%作者自评ARM_NEON的fp16GEMM 内核还不是最优not optimal。这说明方案本身成立x86 端已完全超越 BLASARM 端虽然仍略逊于 BLAS但已经大幅好于原标准模板。3. 实测性能Ryzen 7950X 上的 PP-512 对比PR #482 给出了 Llama-3.1-8B 在Ryzen-7950X上、PP-512prompt 长度 512 token场景下主分支无 BLAS与 PR 的完整对比数据quantPP-512 (main)PP-512 (PR)SpeedupIQ2_KT57.98132.472.28IQ3_KT47.44127.802.69IQ4_KT40.09126.313.15三个量化档位全部获得了2.28x ~ 3.15x的 PP 提速且量化位宽越高IQ4_KT原始反量化负担越重提速越明显。需要说明的是PR #482 明确表示TGtoken generation文本生成性能不受本 PR 影响仍然很低——本方案只针对 prompt processing 阶段。这项成果随后被收录进仓库 README.md 的功能列表Trellis quants: faster CPU prompt processing并成为后续 PR 488Faster CPU prompt processing for Trellis quants and MoE models等进一步优化工作的基础。4. 内核调度从量化类型到内核函数的映射dequantizing GEMM 只是新内核集合的一部分真正决定哪种权重、哪种右矩阵类型用哪个内核的是调度函数iqk_set_kernels_ktquantsiqk_gemm_ktquants.cpp。它的选择逻辑如下if (ne00 % QK_K ! 0) return false; // 列数必须是块大小整数倍 if (typeA IQ1_KT/IQ2_KT/IQ3_KT/IQ4_KT) { if (typeB Q8_2_X4) // 右矩阵为中间格式 Q8_2_X4 → mul_mat_iq{1,2,3,4}_kt_q8_2_x4_T // 重量化路径 } if (typeB ! F32) return false; switch (typeA) { // 右矩阵为 fp32本 PR 核心路径 IQ2_KT → mul_mat_iq2_kt_F32_T IQ3_KT → mul_mat_iq3_kt_F32_T IQ4_KT → mul_mat_iq4_kt_F32_T }ARM 侧iqk_gemm_ktquants.cpp的调度与之平行但方向相反if (typeA IQ4_KT typeB Q8_0_X4) → mul_mat_iq4_kt_q8_0_x4_T if (typeA IQ3_KT typeB Q8_0_X4) → mul_mat_iq3_kt_q8_0_x4_T if (typeA IQ2_KT typeB Q8_0_X4) → mul_mat_iq2_kt_q8_0_x4_T if (typeA IQ1_KT typeB Q8_0_X4) → mul_mat_iq1_kt_q8_0_x4_T if (typeB ! F16) return false; switch (typeA) { // 右矩阵为 fp16ARM 核心路径 IQ2_KT → mul_mat_iq2_kt_F16_T IQ3_KT → mul_mat_iq3_kt_F16_T IQ4_KT → mul_mat_iq4_kt_F16_T }可以看到整个iqk_gemm_ktquants.cpp约 2748 行实际包含了三条技术路线*_F32_T系列x86本 PR 的 dequantizing GEMM反量化到 fp32*_F16_T系列ARM本 PR 的 dequantizing GEMM反量化到 fp16*_q8_2_x4_T/*_q8_0_x4_T系列反量化到Q8_2_X4/Q8_0_X4中间格式后再乘iqk_dequantize_*_q80_r8系列函数属于配套的反量化到 8bit 中间格式变体供需要二次量化的路径复用。4.1 调度入口与调用链这些内核通过两级入口被上层调用见 iqk_mul_mat.cpp 与 iqk_mul_mat.cppcase GGML_TYPE_IQ1_KT: case GGML_TYPE_IQ2_KT: case GGML_TYPE_IQ3_KT: case GGML_TYPE_IQ4_KT: return iqk_set_kernels_ktquants(ne00, typeA, typeB, mm.funcs, mm.func16);反量化函数同样走统一分发iqk_mul_mat.cppcase GGML_TYPE_IQ1_KT: case GGML_TYPE_IQ2_KT: case GGML_TYPE_IQ3_KT: case GGML_TYPE_IQ4_KT: return iqk_dequantize_ktquants(typeA, n, vx, bx, vy, stride_y, nrc_x);其中iqk_dequantize_ktquantsiqk_gemm_ktquants.cpp把 4 种 KT 类型映射到对应的iqk_dequantize_iq{1,2,3,4}_kt_q80_r8函数。4.2 编译开关何时启用这套内核trellis 量化内核并非无条件编译。在 iqk_config.h 中#if defined __AVX2__ || defined __ARM_FEATURE_DOTPROD #define IQK_IMPLEMENT #endifIQK_IMPLEMENT仅在编译目标为x86 且开启 AVX2或ARM 且支持 DOTPROD 扩展时定义iqk_gemm_ktquants.cpp 与 iqk_gemm_ktquants.h 均以#ifdef IQK_IMPLEMENT包裹实现。这意味着在支持 AVX2 的 x86 CPU如 PR 实测的 Ryzen 7950X / Zen4上dequantizing GEMM 自动生效在 ARM如 Apple M 系列、支持 DOTPROD 的移动 SoC上走 fp16 路径在不满足上述条件的 CPU 上这组内核不会参与编译回退到通用实现。5. 数据布局trellis 量化块的存储结构dequantizing GEMM 内核直接操作的是量化块结构理解这些结构有助于看懂反量化代码。块定义位于 ggml-common.h以QK_K为块大小typedef struct { uint8_t sh[QK_K/32]; // 4-bit scales 13th bits for groups of 8 uint8_t ql[QK_K/8]; // low 8 bits for groups of 8 uint8_t qh[QK_K/16]; // high 4 bits for groups of 8 } block_iq1_kt; static_assert(sizeof(block_iq1_kt) QK_K/8 QK_K/16 QK_K/32, wrong iq1_kt block size/padding); typedef struct { uint8_t scales[QK_K/64]; uint8_t ql[QK_K/4]; } block_iq2_kt; static_assert(sizeof(block_iq2_kt) QK_K/4 QK_K/64, wrong iq2_kt block size/padding); typedef struct { uint8_t scales[QK_K/64]; uint8_t ql[QK_K/4]; uint8_t qh[QK_K/8]; } block_iq3_kt; static_assert(sizeof(block_iq3_kt) QK_K/4 QK_K/8 QK_K/64, wrong iq3_kt block size/padding); typedef struct { uint32_t qs[QK_K/8]; } block_iq4_kt; static_assert(sizeof(block_iq4_kt) QK_K/2, wrong iq4_kt block size/padding);要点IQ2_KT每个块只存scales每 64 个权重一组与ql每 4 个权重一个码字是最紧凑的 2bit 级格式IQ3_KT在IQ2_KT基础上增加qh高位域承载 3bit 信息IQ4_KT仅用一个uint32_t数组qs存 4bit 网格码每块 128 个权重只用 64 字节所有结构体都带static_assert校验大小防止布局漂移。以block_iq2_kt为例内核中的反量化读取方式iqk_gemm_ktquants.cppconst uint16_t * ql (const uint16_t *)x[i].ql; // 读码字 auto s8 _mm_set1_epi32(*(const uint32_t *)x[i].scales); // 读 4bit scale 位 // 移位 查 iq4k_values 表得到真实 scale auto xval1 _mm256_mul_ps(scale1, trellis_gen8(trellis.next8(ql[8*ibj0]4096))); // ql[...] 作为 trellis 状态输入next8 递推出 8 个权重每个块的全局 scaled*dptr * 31.75f * 1.05f在累加结束后统一乘回结果从而把反量化 乘加与最终缩放解耦进一步减少每步浮点操作。6. 方案的适用边界与取舍6.1 与 BLAS 的取舍PR #482 的核心决策是与其全局启用 BLAS 换取 trellis 量化一家的加速却拖慢其他所有数据类型不如为 trellis 量化单独实现 dequantizing GEMM。这样trellis 量化IQ2_KT/IQ3_KT/IQ4_KT获得 2~3 倍 PP 加速其他数据类型完全不受影响无需承担 BLAS 的通用性损失x86 平台Zen4/AVX2最终表现优于BLAS / Intel MKL。6.2 已知限制只加速 PPTG 性能不受影响仍然很低。PP 是矩阵-矩阵乘GEMM主导TG 是矩阵-向量乘GEMV主导本方案的高摊薄特性只在前者中成立ARM 端仍有提升空间M2 Max 上约为 BLAS 的 80%作者明确指出 NEON fp16 GEMM 内核尚非最优依赖 AVX2 / DOTPRODIQK_IMPLEMENT宏要求编译目标具备相应指令集否则不会编译这组内核。6.3 延伸阅读想继续深入可在当前仓库中查阅完整实现iqk_gemm_ktquants.cppx86 与 ARM 两套 dequantizing GEMM 内核、iqk_gemm_ktquants.h对外接口调度与分发iqk_mul_mat.cppiqk_set_kernels_ktquants调用点、iqk_mul_mat.cppiqk_dequantize_ktquants调用点块结构与量化入口ggml-common.h块定义、iqk_quantize.hquantize_row_iq{1,2,3,4}_kt等函数声明编译条件iqk_config.hIQK_IMPLEMENT定义项目层面的量化功能综述README.mdTrellis quants 小节与 README.md相关 PR 列表。7. 总结PR #482 为 ik_llama.cpp 的 trellis 量化在 CPU 上的 prompt processing 打开了一条全新的优化路径放弃每个块只乘 8 列的标准模板改为把 trellis 码字实时反量化为 fp32x86/AVX2或 fp16ARM/NEON然后用整列复用的浮点 GEMM 内核完成矩阵乘。这一设计让高昂的反量化成本被整个右矩阵摊销在 Ryzen 7950X 上将IQ2_KT / IQ3_KT / IQ4_KT的 PP-512 吞吐率分别提升了 2.28x / 2.69x / 3.15x且 x86 端全面超越 BLAS同时以不动其他数据类型的方式规避了 BLAS 的通用性缺陷。从源码视角看这组内核在 iqk_gemm_ktquants.cpp 中按架构双轨实现由 iqk_set_kernels_ktquants 与 iqk_set_kernels_ktquantsARM 统一调度并由IQK_IMPLEMENTAVX2/DOTPROD控制编译开关。这套dequantizing GEMM思想此后被延续到 PR 488Trellis 量化与 MoE 模型的进一步 PP 优化等后续工作中成为 ik_llama.cpp CPU 侧量化加速的重要方法论之一。赞分享人工智能大模型推理引擎本地部署模型量化模型优化【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp点击查看免费下载相关推荐ik_llama.cpp Trellis 量化与 MoE 模型的 CPU 预填充加速PR 488 dequantizing GEMM 方案剖析ik_llama.cpp Trellis 量化与 MoE 模型的 CPU 预填充加速PR 488 dequantizing GEMM 方案剖析 本文围绕 ik人工智能大模型推理引擎本地部署模型量化模型优化BF16 Trellis 量化加速ik_llama.cpp 的 IQ2_KT / IQ3_KT / IQ4_KT CPU 推理优化解析BF16 Trellis 量化加速ik_llama.cpp 的 IQ2_KT / IQ3_KT / IQ4_KT CPU 推理优化解析 BF16bfloat人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速PR 186 源码级解析与实测数据ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速PR 186 源码级解析与实测数据 导读 本文以 ik_lla人工智能大模型推理引擎本地部署模型量化模型优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表