尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ik_llama.cpp PR 119 深度解析:Q4_0_R4 四行交错重打包如何将 Prompt 处理提速 1.5 倍

ik_llama.cpp PR 119 深度解析:Q4_0_R4 四行交错重打包如何将 Prompt 处理提速 1.5 倍 ik_llama.cpp PR #119 深度解析Q4_0_R4 四行交错重打包如何将 Prompt 处理提速 1.5 倍【免费下载链接】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 #119「Q4_0_R4」展开剖析其核心思路将经典的Q4_04-bit 量化格式按 4 行交错重打包为IQ4_NL_X4即仓库中最终落地的IQ4_NL_R4类型从而显著改善 CPU 上 prompt processing 的访存与向量化效率。读完本文你将理解行交错row-interleaved重打包的底层数据布局、block_iq4_nl_r4结构与 repack 实现原理、PR 中三平台实测数据最高 1.496× 加速以及该思路与 mainline llama.cppQ4_0_4_4方案的取舍差异并掌握如何在当前仓库中量化、重打包与使用IQ4_NL_R4模型。一、PR 背景为什么 Q4_0 需要重打包Q4_0是 llama.cpp 生态中最经典、兼容性最好的 4-bit 量化格式之一。它按 32 个元素QK4_NL 32为一个 block每个 block 携带一个 fp16 缩放因子d和 16 字节的 4-bit 量化值。其优点是与Q8_0激活量化做向量点积vec_dot时格式简单、任何后端都能支持缺点是逐行独立存储、按行串行处理在 CPU 上做矩阵乘法时访存局部性和向量寄存器利用率并非最优。ik_llama.cpp 的思路来自其特有的 R4 / R8 重打包家族把多行权重按行交错重新排布让单个 kernel 一次性处理多行数据摊薄缩放因子读取与地址计算开销并为 SIMD 宽寄存器提供更规整的载荷。PR #119 就是把Q4_0即仓库内部的IQ4_NL类型按 4 行交错重打包作者在 PR 描述中直接称其为IQ4_NL_X4Q4_0repacked with 4 interleaved rows asIQ4_NL_X4该方案最终以GGML_TYPE_IQ4_NL_R4类型的形式完整落地到仓库中。从源码看这类重打包类型在仓库中是一个成体系的家族。在 ggml/src/iqk/iqk_mul_mat.cpp 的行数映射中可以看到IQ2_S_R4、IQ3_XXS_R4、IQ1_S_R4、IQ4_NL_R4、Q4_K_R4、Q5_K_R4、Q6_K_R4对应 4 行重排Q4_0_R8、Q8_0_R8、IQ4_XS_R8、Q8_K_R8等对应 8 行重排而Q8_0_R16、MXFP4_R8等甚至做到 16 行。Q4_0_R4正是这一思路在Q4_0上的首发验证。二、核心数据结构block_iq4_nl_r4 的存储布局在重打包后的格式中4 行的数据被合并进同一个 block。其结构体定义在 ggml/src/ggml-common.h// Non-linear quants #define QK4_NL 32 typedef struct { ggml_half d; uint8_t qs[QK4_NL/2]; // 16 字节32 个 4-bit 值 } block_iq4_nl; // 原始 Q4_0 / IQ4_NL block2 16 18 字节 typedef struct { ggml_half d[4]; // 4 个 fp16 缩放因子分别对应 4 行 uint8_t qs[2*QK4_NL]; // 64 字节存 4 行 × 32 个 4-bit 值 } block_iq4_nl_r4; // 重打包 block8 64 72 字节 static_assert(sizeof(block_iq4_nl_r4) 4*sizeof(ggml_half) 2*QK4_NL, wrong iq4_nl_r4 block size/padding);对照关系一目了然原始block_iq4_nl每行一个 block18 字节block_iq4_nl_r4把 4 行的 4 个缩放因子集中存放在 block 头部d[4]8 字节随后是 64 字节的量化数据区。由于每一行仍然只有 32 个 4-bit 值按行/按 block 的数据总量不变——重打包不改变模型的存储大小bit-per-weight 不变只改变内存中的排列方式这正是它可以作为在线重打包不重新量化、不损失精度手段的前提。该类型在 ggml 类型注册表中的元信息位于 ggml/src/ggml.ctype_name iq4_nl_r4blck_size QK4_NL注册了对应的to_float反量化、from_float/from_float_ref量化以及vec_dot vec_dot_iq4_nl_r4_q8_0当开启GGML_USE_IQK_MULMAT时还会挂载 AVX2 等 SIMD 专用路径。三、repack 实现4 行数据如何交错重打包的核心函数是 ggml/src/iqk/iqk_quantize.cpp 中的repack_iq4_nlstatic void repack_iq4_nl(int nrows, int n_per_row, const block_iq4_nl * x, block_iq4_nl_r4 * y, bool online) { GGML_ASSERT(nrows%4 0); GGML_ASSERT(n_per_row%QK4_NL 0); int nblock n_per_row/QK4_NL; const block_iq4_nl * x4[4]; for (int row 0; row nrows; row 4) { for (int k 0; k 4; k) x4[k] x nblock*k; for (int ib 0; ib nblock; ib) { for (int k 0; k 4; k) y[ib].d[k] x4[k][ib].d; for (int k 0; k 4; k) for (int i 0; i 4; i) { y[ib].qs[4*ki 0] (x4[k][ib].qs[i0] 0xf) | ((x4[k][ib].qs[i 8] 0x0f) 4); // 0....3 8...11 y[ib].qs[4*ki16] (x4[k][ib].qs[i0] 4) | ((x4[k][ib].qs[i 8] 0xf0)); // 16...19 24...27 y[ib].qs[4*ki32] (x4[k][ib].qs[i4] 0xf) | ((x4[k][ib].qs[i12] 0x0f) 4); // 4....7 12...15 y[ib].qs[4*ki48] (x4[k][ib].qs[i4] 4) | ((x4[k][ib].qs[i12] 0xf0)); // 20...23 28...31 } } x 4*nblock; y nblock; } }这段代码清晰展示了交错的含义每次取连续 4 行的 block 序列x4[0..3]4 个缩放因子原样搬到目标 block 头部d[4]不引入任何精度损失量化字节区按每行 8 字节32 个 4-bit 值切分用位运算把每行第0..3、4..7、8..11、12..15字节的低/高 4-bit 抽取出来重新组合进目标 block 的 4 个 16 字节区段——即每行的值被均匀打散到新 block 的固定偏移上从而让一个 SIMD 向量可以同时覆盖 4 行同一位置的数据。对应的量化入口quantize_iq4_nl_r4先按 4 行一组调用quantize_iq4_nl得到临时Q4_0数据再立即repack_iq4_nl完成重排一步到位生成 R4 布局。这也解释了为什么它能同时用于离线量化与在线重打包两种场景。四、PR #119 实测性能数据原表完整保留PR 作者 ikawrakow 在 PR 描述中给出了 LLaMA-3.1-8B 模型、prompt processing 长度为 512 tokenPP-512下三个平台、三种指令集的实际测量结果PlatformThreadsQ4_0 (t/s)Q4_0_R4 (t/s)SpeedupARM_NEON (M2-Max)884.57 ± 0.94115.79 ± 0.861.369Zen4 (Ryzen-7950X)16185.89 ± 0.84278.15 ± 0.391.496AVX2 (Ryzen-5975WX)32190.73 ± 0.39251.00 ± 0.511.316关键结论均为 PR 作者在特定硬件上的测量结果供参考x86_64 上的收益高于 ARMZen4 提速 1.496×、AVX2 提速 1.316×均超过 M2-Max 的 1.369×。这与 R4 布局能更好地利用 AVX2 的 256-bit 寄存器、减少每行地址计算有关作者特别指出OnZen4Q4_0_R4is now the prompt processing champion.——在该平台上重打包后的Q4_0_R4成为 prompt processing 的冠军格式三组数据都带 ± 标准差说明测试是多次重复取均值具有可信度。需要说明的是PR #119 的状态为Closed。但从当前仓库源码看该方案并没有被放弃而是以GGML_TYPE_IQ4_NL_R4/LLAMA_FTYPE_MOSTLY_IQ4_NL_R4枚举值 225见 include/llama.h的完整形态保留并持续演进后续还衍生出了IQ4_XS_R8、Q8_K_R8等更高行数的变体。五、与 mainline Q4_0_4_4 的对比0x88 xor mask 的取舍PR 描述中还包含一段重要的技术对比mainline llama.cpp 的手写汇编Q4_0_4_4在作者的 M2-Max 上测得122.8 t/s比Q4_0_R4的 115.79 t/s 略快。作者给出了自己的分析我猜测Q4_0_4_4稍好是因为它在打包阶段就应用了0x88xor mask——这个掩码能把无符号的 4-bit 量化值转换成带符号的 4-bit 值并左移 4 位从而在点积时直接复用有符号乘法路径。但作者明确表示没有采用这一技巧理由是this trick is only useful for theARMinstruction set, and is absolutely not useful onx86_64。在 x86_64 上符号扩展可以通过已有的有符号/无符号混合乘法指令低成本完成预先把掩码折进权重反而会破坏数据布局的通用性。换句话说这是一个针对指令集特性的局部优化决策ARM 下收益明显、x86 下无收益而 ik_llama.cpp 要覆盖 NEON、AVX2、Zen4 等多平台选择保持 R4 布局的通用性、把优化放到各平台的 kernel 层配合 IQK 后端分别实现。六、如何在当前仓库中使用 IQ4_NL_R41. 直接量化离线通过 llama-quantize 工具量化模型时可将输出类型指定为IQ4_NL_R4对应 ftype 225LLAMA_FTYPE_MOSTLY_IQ4_NL_R4见 include/llama.h。量化入口即上文所述的quantize_iq4_nl_r4其内部会以 4 行为一组完成Q4_0量化 交错重打包生成的 GGUF 中权重张量直接以 R4 布局存储。在 src/llama-quantize.cpp 中还登记了 R4 家族的重打包映射{ GGML_TYPE_IQ4_NL_R4, { GGML_TYPE_IQ4_NL, 4} },含义是IQ4_NL_R4可以由IQ4_NL即Q4_0按 4 行一组重打包而来。2. 在线重打包不重新量化由于 R4 重打包不改变量化误差缩放因子原样搬运、量化值仅做位重排仓库支持把已有Q4_0模型在线重打包为IQ4_NL_R4。相关路径包括src/llama-quantize.cpp{ LLAMA_FTYPE_MOSTLY_IQ4_NL, LLAMA_FTYPE_MOSTLY_IQ4_NL_R4 }的 ftype 转换映射src/llama-quantize.cpp加载MOSTLY_IQ4_NL_R4模型时对不支持类型做退化fallback处理例如在需要时转回Q5_K保证兼容性ggml/src/iqk/iqk_quantize.cpp登记了GGML_TYPE_IQ4_NL→GGML_TYPE_IQ4_NL_R4factor 4的离线重打包函数repack_iq4_nl供 gguf 层面的重打包工具调用。这意味着你既可以在量化阶段直接产出IQ4_NL_R4模型也可以对已有的Q4_0权重在加载/转换时无损地完成重打包获得与 PR #119 相同格式的性能收益。3. 底层计算路径IQ4_NL_R4的矩阵乘法由 IQK 后端承担激活侧量化后与Q8_0/Q8_0_R8配对计算见 ggml/src/iqk/iqk_mul_mat.cpp 中的类型映射在 ggml/src/iqk/iqk_gemm_legacy_quants.cpp 中IQ4_NL_R4_Dequantizer使用 NEON 的vld1q_s8一次加载 16 字节向量进行反量化配合mul_mat_qx_r4_q8_0通用 R4 模板 kernel从而在多平台上复用同一套交错处理逻辑CUDA 后端也在 ggml/src/ggml-cuda.cu 中登记了IQ4_NL_R4的支持。七、总结与后续演进PR #119 的核心贡献可以概括为三点验证了行交错重打包对 4-bit 权重的普适价值不动量化精度仅改内存布局即可在 NEON/Zen4/AVX2 上分别获得 1.37~1.50× 的 prompt processing 提速明确了平台差异化的优化边界通过与 mainlineQ4_0_4_4的对比说明了0x88xor mask 这类 ARM 专属技巧在 x86_64 上无收益重打包格式应保持指令集中立、把平台特化放到 kernel 层沉淀为完整的量化类型IQ4_NL_R4已具备量化、反量化、vec_dot、在线/离线重打包、CUDA 支持的全链路实现并带动了Q4_K_R4、Q5_K_R4、Q6_K_R4、IQ4_XS_R8等 R4/R8 家族的发展。如果你正在 CPU 上运行Q4_0量化的模型并希望提升 prompt processing 吞吐IQ4_NL_R4是当前仓库中一个零精度损失、纯布局优化的直接升级路径后续对重打包家族感兴趣的读者可以顺着 ggml/src/ggml-common.h 中的 block 定义和 ggml/src/iqk/iqk_quantize.cpp 中的 repack 登记表继续深入。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表