资讯详情

ik_llama.cpp 量化精度修复实录:用 Q8_K_128 替代 Q8_1 根治 IQ1_S_R4 / IQ1_M_R4 的 fp16 溢出

📅 2026/9/19 20:50:44 | 华诺云谱 👁 阅读
ik_llama.cpp 量化精度修复实录:用 Q8_K_128 替代 Q8_1 根治 IQ1_S_R4 / IQ1_M_R4 的 fp16 溢出
ik_llama.cpp 量化精度修复实录用 Q8_K_128 替代 Q8_1 根治 IQ1_S_R4 / IQ1_M_R4 的 fp16 溢出【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本文以 ik_llama.cpp 仓库中编号 194 的 PR 文档为核心完整还原了作者 ikawrakow 针对 DeepSeek-R1 使用IQ1_S_R4量化后出现 NaN 输出这一疑难问题的定位过程与修复方案为何Q8_1作为激活量化格式会在 fp16 范围内悄悄溢出以及引入 128 块尺寸的新 8-bit 量化格式Q8_K_128如何从根源上消除该隐患。读完本文你将理解 1-bit 级量化1.5/1.75 bpw推理路径中激活量化的数值稳定性设计、AVX2 三元量化点积的精度陷阱以及该修复在真实 DeepSeek-R1 模型上的验证结果。背景DeepSeek-R1 的 1-bit 量化与变笨疑云2025 年初DeepSeek-R1 引爆了开源社区对超大 MoE 模型的本地推理热情。为了让 671B 参数级别的模型能在个人硬件上运行社区普遍采用低于 2 bpwbits per weight的激进量化方案。ik_llama.cpp 在 PR #185 中引入了IQ1_S_R41.5 bpw4 行交错版IQ1_S随后在 PR #187 引入IQ1_M_R41.75 bpw4 行交错版IQ1_M。这两种格式比主流的IQ1_S1.5625 bpw更省空间且因为块尺寸降到 32能覆盖更多行大小非 256 倍数、原本会被降级为IQ4_NL的张量见 PR #185。然而社区用户 saood06 在实测中发现使用IQ1_S_R4量化的 DeepSeek-R1 在推理时持续出现 NaN 输出。与此同时社区里流传着 DeepSeek-R1 在使用 fp16 存储部分注意力张量时会变笨输出质量大幅下降的讨论——两者是否同源成为 PR #194 要验证的核心假设。修复前的数值隐患Q8_1 激活量化的 fp16 溢出路径在 iqk 风格的 GEMM 实现ggml/src/iqk/iqk_gemm_1bit.cpp中1-bit 权重IQ1_S_R4/IQ1_M_R4与 8-bit 激活Q8_1/Q8_0做点积时需要先把 fp32 激活转换成 8-bit 量化表示。PR #194 的作者提出了一个层层递进的假设链激活超出 fp16 范围如果某些激活值超出 fp16 可表示的最大值约 65504那么在从 fp32 转换到 fp16为了与 fp16 权重做乘法的过程中会发生截断。Q8_1 的块尺度 d 通常是安全的假设某块 32 个激活的最大值x_max f16_max但块尺度d x_max/127落在 fp16 范围内——作者指出这很可能成立因为Q8_0注意力量化张量的表现优于 fp16 正是这个原因。致命点在 Q8_1 的求和项 sQ8_1除了块尺度 d 外还会计算s d * Σqᵢqᵢ 为 8-bit 量化值并且 s 也以 fp16 存储。即使 d 在 fp16 范围内如果运气不好s 依然可能溢出。溢出导致点积结果完全错误为了在 AVX2 上提速IQ1_S_R4使用三元量化值{0, 1, 2}而非{-1, 0, 1}与 Q8 量化值相乘以复用_mm256_maddubs_epi16指令见 iqk_gemm_1bit.cpp 中大量_mm256_maddubs_epi16的调用随后通过减去 s 来恢复正确结果。一旦 s 因超出 fp16 范围而被截断这个减 s校正失效该块的点积结果就完全错误——NaN 由此而来。换句话说Q8_1的块尺度 d 在 fp16 内但无尺度化的求和项 s 会越界这是此前被忽略的盲区。修复方案引入 128 块尺寸的 Q8_K_128针对上述假设PR #194 的做法是为IQ1_S_R4和IQ1_M_R4的矩阵乘法引入一个新的 8-bit 量化格式Q8_K_128结构与Q8_K类似但块尺寸为 128作者需要这个尺寸以便在 DeepSeek-Lite 上做验证测试。尺度精度使用 32-bit float 块尺度而不是 fp16。求和方式每 32 个元素一组求和结果以int16_t存储且不乘以 d——这就彻底绕开了 fp16 范围问题任何情况下都不会再出现 16-bit 浮点溢出。这一点在当前仓库的代码中得到了印证。在 ggml/include/ggml.h 中GGML_TYPE_Q8_K128 150与Q8_1GGML_TYPE_Q8_1 9、Q8_0GGML_TYPE_Q8_0 8并列存在而在 ggml/src/iqk/iqk_gemm_1bit.cpp 的内核选择逻辑iqk_set_kernels_1bit中IQ1_S_R4分支要求ne00 % 128 0并设置expected_typeB GGML_TYPE_Q8_K128IQ1_M_R4分支同样如此ARM_NEON 路径下两者同样指向Q8_K128——即这两个 1-bit 格式的 8-bit 激活侧同伴正是Q8_K_128。这从源码结构上证实了 PR #194 的修复在最终合入版本中的形态。作者在文档中还给出了一个耐人寻味的观测DeepSeek-Lite 上使用Q8_K_128的困惑度perplexity相比Q8_1略有降低。按常理Q8_0/Q8_1因为块尺寸更小32 对 128应该有更高的精度这里 perplexity 反而更好恰恰说明Q8_1路径上存在即使不致命不产生 NaN的截断效应——非致命截断在精度上也会留下痕迹。验证结果DeepSeek-R1 上的真实数据PR #194 是 draft 状态提交的原因正如作者说明我还没做 ARM_NEON 实现当时只有 AVX2/x86 路径。作者在文档中公开征求 DeepSeek-R1 的实测saood06 当天便完成了验证报告了 64 个 chunk 的困惑度序列[1]3.7099,[2]4.6162,[3]3.5438,[4]3.4199,[5]3.5375,[6]3.5710,[7]3.5428,[8]3.6748, [9]3.7417,[10]3.6724,[11]3.7879,[12]3.9602,[13]4.0477,[14]4.1439,[15]4.2809, [16]4.1981,[17]4.3853,[18]4.5141,[19]4.4493,[20]4.3848,[21]4.4664,[22]4.3290, [23]4.1912,[24]4.1799,[25]4.0693,[26]4.0135,[27]4.0672,[28]4.0459,[29]4.1110, [30]4.1116,[31]4.1261,[32]4.1192,[33]4.1756,[34]4.2340,[35]4.3112,[36]4.3722, [37]4.3822,[38]4.4260,[39]4.4568,[40]4.5164,[41]4.5661,[42]4.5563,[43]4.5975, [44]4.5821,[45]4.6738,[46]4.7199,[47]4.7029,[48]4.6934,[49]4.6900,[50]4.7087, [51]4.7637,[52]4.7736,[53]4.8515,[54]4.8776,[55]4.9119,[56]4.9504,[57]4.9769, [58]5.0124,[59]5.0024,[60]5.0545,[61]5.1015,[62]5.1639,[63]5.2095,[64]5.2599结论明确No more NaNs, nice!不再出现 NaN且困惑度曲线平稳、符合长序列推理的典型递增形态证明Q8_K_128修复后模型输出数值稳定、质量正常。作者也在 2025-02-09 的回复中确认破案的关键线索正是 saood06 提醒的DeepSeek-R1 在 fp16 注意力张量下变笨的社区讨论。技术启示与后续演进这次修复的价值远超修一个 bug本身它揭示了一条通用的量化工程原则量化格式的数值稳定性不能只看尺度还要看辅助量sum 项的表示范围。Q8_1的 s 项虽然只是中间产物但其 fp16 存储格式在极端激活分布下会成为精度与正确性的瓶颈。对超低 bit 量化1-2 bpw而言激活侧的表示精度直接决定推理正确性。激活不像权重那样可以离线精心调优它依赖运行时转换任何范围假设的失效都会立刻表现为 NaN 或输出退化。以 fp32 尺度 整型累加不乘 d存储 sum是用存储换正确性的典型取舍后续Q8_K64、Q8_K16、Q8_0_X4等格式家族在 ggml/include/ggml.h 与 ggml/src/iqk/iqk_gemm_1bit.cpp 中不断扩充也延续了这一思路。值得补充的是IQ1_S_R4/IQ1_M_R4的旅程并未止步于此后续 PR #492 / #494 为它们补齐了 CUDA 实现见 README.md 的量化类型清单PR #371 则修复了其在 Qwen3-235B-A22B 等模型上因近零权重块导致的量化断言失败问题见 issue #367。这些迭代共同构成了 ik_llama.cpp 在 SOTA 低 bit 量化方向的完整技术脉络。对于想要复现或深入研究的读者建议从两个入口切入量化器实现位于 ggml/src/iqk/iqk_quantize.cpp1-bit GEMM 内核与激活转换逻辑位于 ggml/src/iqk/iqk_gemm_1bit.cpp格式枚举与类型特性定义在 ggml/include/ggml.h。结合 PR #185 与 PR #187 中关于设计动机与提速数据的描述即可完整拼出 1-bit 量化从设计到数值稳定的全貌。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。