拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ik_llama.cpp 激活量化重构:移除 Q8_1 的 fp16 尺度上限,兼容超大激活值

ik_llama.cpp 激活量化重构移除 Q8_1 的 fp16 尺度上限兼容超大激活值【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp导读本文围绕 ik_llama.cppllama.cpp 的一个性能增强 fork见 README.md中 #196 这一重构议题展开当模型产生超出fp16数值范围的激活值时使用Q8_1量化激活会失效。文章先解释Q8_1在 ggml 张量类型体系中的定位与块结构再结合仓库源码分析Q8_1参与矩阵乘法vec_dot的全部调用路径最后给出两种重构方向的实现依据与评估。读完本文你将理解激活量化的精度瓶颈、Q8_1与Q8_0/Q8_2系列类型的替代关系以及如何从源码层面对量化类型进行替换或改造。背景为什么fp16范围内的激活量化会失效在 ggml/llama.cpp 生态中矩阵乘法通常采用「权重静态量化 激活动态量化」的组合权重src0在模型转换阶段就量化为Q4_0、Q4_K、Q5_1、Q6_0等低位类型参数固定不变激活src1在每次前向传播时动态量化。为了与低位权重点积激活常被量化成Q8_0或Q8_18-bit 对称/非对称量化再由各类型的vec_dot例程完成点积并累加。issue #196 指出某些模型产生的激活值可以超出fp16的表示范围。此时用Q8_1量化激活会「futile」无效/失效因为它用来存储块尺度d和块内和m/s的字段是ggml_half即 fp16一旦激活绝对值超过 fp16 上限约 65504或需要 fp16 无法表达的精度量化结果就会失真甚至溢出。该讨论关联到 issue #194 中的分析相关 GitHub 议题镜像见 github-data/issues 目录二者共同点在于fp16 作为「元数据载体」在量化链路中是不够稳的。理解Q8_1的块结构fp16 尺度与块内和Q8_1是非对称 8-bit 量化类型其块定义在 ggml/src/ggml-common.h#define QK8_1 32 typedef struct { GGML_SCALE_TYPE1(s, ds); int8_t qs[QK8_1]; // quants } block_q8_1; static_assert(sizeof(block_q8_1) 2*sizeof(ggml_half) QK8_1, wrong q8_1 block size/padding);其中GGML_SCALE_TYPE1在大多数后端如 ggml-common.h 的定义展开为两个ggml_halffp16字段ddelta缩放尺度和m最小值/偏移即块内和。这正对应 issue 中提到的「fp16block scale and block sum」Q8_1的每个 32 元素块用两个 fp16 来记录「尺度」与「均值偏移」然后只存 32 个 int8 量化值。对比之下对称量化的Q8_0ggml-common.h只含一个 fp16 尺度d加 int8 数据没有块内和字段。非对称的Q8_1多出的m字段让它能更好地处理偏置分布但代价就是 fp16 参与了「尺度偏移」两处元数据的存储fp16 的数值上限与精度限制被放大了。源码追踪哪些矩阵乘法路径在使用Q8_1要重构Q8_1第一步是搞清它在仓库中的全部用途。从 ggml/src/ggml.c 的类型表ggml_type_traits可以看到Q8_1作为激活的vec_dot_type被大量引用Q4_1的vec_dot_type默认是Q8_1非 IQK_MULMAT 路径ggml.cQ5_1的vec_dot_type默认是Q8_1ggml.cQ4_K、Q5_K等 K-quant 类型在特定后端同样以Q8_1作为激活点积类型Q8_1自身的vec_dot_type也是Q8_1ggml.c。在 ik_llama.cpp 的 IQK 内核GGML_USE_IQK_MULMAT编译开关中ggml/src/iqk/iqk_mul_mat.cpp 的 mul_mat 类型选择逻辑也大量返回Q8_1作为激活类型例如case GGML_TYPE_Q4_K : return nrc_y 32 ? GGML_TYPE_Q8_1 : type; case GGML_TYPE_Q5_K : return nrc_y 32 ? GGML_TYPE_Q8_1 : type; case GGML_TYPE_Q4_1 : return nrc_y 32 ? GGML_TYPE_Q8_1 : type; case GGML_TYPE_Q5_1 : return nrc_y 32 ? GGML_TYPE_Q8_1 : type;见 iqk_mul_mat.cpp——即当行数足够时nrc_y 32K-quant 与 legacy 类型的激活统一量化为Q8_1。此外Q8_1还出现在ggml/src/ggml-quants.c 的ggml_vec_dot_q4_1_q8_1、ggml_vec_dot_q5_1_q8_1等点积例程对应vec_dot字段CUDA 后端 ggml/src/ggml-cuda.cu、ggml/src/ggml-cuda/mmvq.cu 中激活的量化与点积模板block_q8_1/block_q8_1_mmq见 ggml-cuda/mmq.cuhVulkan 后端 ggml/src/ggml-vulkan.cpp 中的类型映射。可以推断Q8_1在 CPU、CUDA、Vulkan 三条后端路径中都承担着「激活侧非对称量化」的角色重构必须同时覆盖这三个层面否则会出现部分算子仍走旧路径的不一致状态。重构方向一为激活类型引入替代量化方案issue 提出的第一个方向是「把所有用Q8_1做矩阵乘法的量化类型换成别的类型」。仓库中现成的替代品包括Q8_0对称量化只有 fp16 尺度d无块内和字段。它在Q4_0/Q5_0/Q6_0/Q8_0的vec_dot_type中已是默认选择见 ggml.c并可与 IQK 路径下的Q8_0_X44-block 打包ggml.c配合。切换成本最低但非对称信息块均值丢失对偏置明显分布的激活精度可能略降。Q8_1_X4/Q8_2_X4在GGML_USE_IQK_MULMAT下Q4_1/Q5_1在非 AVX2 平台使用Q8_1_X4在 AVX2 平台使用Q8_2_X4见 ggml.c 与 ggml.c。这些 X4 类型把多个块打包成 4 块一组的宽 SIMD 布局但底层元数据仍是 fp16。也就是说单纯「换类型」只能缓解无法根治 fp16 元数据的上限问题——除非目标类型本身不再用 fp16 存尺度。重构方向二把Q8_1的块尺度/块内和从fp16换成bf16issue 提出的第二个方向是将Q8_1的块尺度block scale与块内和block sum从fp16替换为bf16。bf16bfloat16与 fp16 占用相同的 16-bit 存储但保留了与 fp32 一致的 8 位指数位取值范围与 fp32 相同约 ±3.4e38远大于 fp16 的 ±65504。因此对于「超出 fp16 范围的大激活值」bf16尺度不会溢出这正是 issue 反复强调 fp16 上限问题的根源所在。代价是 bf16 尾数只有 7 位对「小尺度、高精度」的场景相对误差可能比 fp1610 位尾数略大。但作为块级尺度/偏移的元数据其精度通常足以维持点积结果的相对误差在可接受范围内因为 int8 量化本身的主导误差来源是块内数据的离散化而非尺度的表示精度。从仓库实现看ik_llama.cpp 已经存在bf16相关的张量类型与路径例如 ggml/src/ggml.c 中的GGML_TYPE_BF16及BF16_R16等变体iqk_mul_mat.cpp 也有对应的块类型分支说明bf16在该代码库中是成熟可用的类型体系成员将block_q8_1的d/m字段换成bf16具备落地基础。重构的完整工作量清单结合源码盘点综合上述分析一次完整的Q8_1去 fp16 化重构至少涉及块结构定义修改 ggml/src/ggml-common.h 中block_q8_1的GGML_SCALE_TYPE1元数据字段与 CPU/SYCL 共享量化/反量化例程quantize_row_q8_1与相关to_float路径ggml.c 注册的from_float/from_float_ref以及 ggml/src/ggml-quants.c 中的q8_1量化实现点积内核ggml_vec_dot_q4_1_q8_1、ggml_vec_dot_q5_1_q8_1等vec_dot例程ggml-quants.c以及 IQK 路径 ggml/src/iqk/iqk_mul_mat.cpp 中的对应分支CUDA 后端block_q8_1_mmq与激活量化内核ggml/src/ggml-cuda/quantize.cu、ggml/src/ggml-cuda/mmq.cuh、ggml/src/ggml-cuda/mmvq.cu以及模板实例 ggml/src/ggml-cuda/template-instances/Vulkan 后端ggml/src/ggml-vulkan.cpp 中的类型/着色器映射。从当前代码状态看issue 状态为Closed2025-02-09 创建、2025-03-27 更新该重构方向已在 ik_llama.cpp 的后续演进中落地为更宽泛的激活类型选择策略现代版本中vec_dot_type的选择会根据指令集AVX2、ARM MATMUL_INT8与行数nrc_y在Q8_0、Q8_1、Q8_0_X4、Q8_1_X4、Q8_2_X4之间动态切换并通过GGML_USE_IQK_MULMAT编译开关启用见 ggml.c 的类型表与 iqk_mul_mat.cpp 的类型选择函数这为处理超出 fp16 范围的激活提供了多档容错。结语issue #196 的价值在于指出了激活量化链路中一个容易被忽略的精度陷阱即使量化粒度足够细只要承载尺度/偏移的元数据格式fp16先于 int8 数据饱和整个量化就失去意义。对这一问题的处理既可以直接为Q8_1引入bf16元数据方向二改动集中、风险可控也可以在更大范围内以Q8_0系列或 X4 打包类型替换方向一改动面大、收益取决于后端覆盖度。读者可在 ggml/src/ggml-common.h、ggml/src/ggml.c、ggml/src/iqk/iqk_mul_mat.cpp 中进一步核对本文引用的块结构与类型选择逻辑结合自己的模型实测激活分布后再决定采用哪种方案。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门