ik_llama.cpp 量化权重之谜:weight[j] = qw[j] * sqrtf(sigma2 + xb[j]*xb[j])的由来与原理
【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
导读
本文围绕 ik_llama.cpp 量化器源码中反复出现的一行加权公式展开——weight[j] = qw[j] * sqrtf(sigma2 + xb[j]*xb[j])。它出现在 Q4_0、Q4_1、Q5_0、Q5_1、Q6_0 等经典量化类型,以及本仓库特有的 i-quants / IQx_KS 系列量化实现中,是"基于重要性矩阵(imatrix)的加权 RMSE 量化"的核心修正项。读完本文,你将理解 imatrix 量化为什么不能直接使用weight[j] = qw[j]、sqrt(sigma² + x²)这一经验修正的取舍逻辑,以及当模型为 DeepSeek、Arctic 这类高专家数 MoE 时,如何用--override-kv expert_used_count改进 imatrix 数据质量。
一、问题缘起:量化器里的weight[j]是什么
该问题最初来自仓库讨论区的一则提问(见 github-data/discussions/140 - Questions about weight_j_.md):提问者在阅读quantize_row_q4_0_impl等函数时发现,每个被量化元素 j 的权重被写成了
weight[j] = qw[j] * sqrtf(sigma2 + xb[j]*xb[j]);而不是更直白的
weight[j] = qw[j];这里的符号含义,可以从源码中逐一确认:
qw[j]:从 imatrix(重要性矩阵)数据中读取的、与该元素一一对应的量化权重。即 examples/imatrix/imatrix.cpp 计算出的对角 Hessian 近似值;xb[j]:当前量化块内第 j 个原始浮点权重值;sigma2:整行权重的平均平方和,即sum_x2 / n_per_row,其中sum_x2 = Σ x[j]²。
以 ggml/src/ggml-quants.c 中的quantize_row_q4_0_impl为例,完整流程是:
float sum_x2 = 0; for (int j = 0; j < n_per_row; ++j) sum_x2 += x[j]*x[j]; float sigma2 = sum_x2/n_per_row; for (int ib = 0; ib < nb; ++ib) { const float * xb = x + QK4_0 * ib; for (int j = 0; j < QK4_0; ++j) weight[j] = qw[j] * sqrtf(sigma2 + xb[j]*xb[j]); float d = make_qx_quants(QK4_0, 8, xb, L, 1, weight); ... }同样的公式还出现在quantize_row_q4_1_impl、quantize_row_q5_0_impl、quantize_row_q5_1_impl、quantize_row_q6_0_impl(ggml/src/ggml-quants.c、ggml/src/ggml-quants.c、ggml/src/ggml-quants.c、ggml/src/ggml-quants.c),以及本仓库特色的 ggml/src/iqk/iqk_quantize.cpp(IQ2_K、IQ3_K、IQ4_K、IQ5_K、IQ6_K 等)中,说明这是整套加权量化路径上的通用约定。
weight[j]最终会被送入make_qx_quants(ggml/src/ggml-quants.c)。该函数在求解最优量化 scale 时,把加权项融入"加权最小二乘"目标:累加sumlx += w*x[i]*l、suml2 += w*l*l,最优 scale 取sumlx/suml2,并通过贪婪翻转迭代(g = scale * w * (x[j] - scale*l))逐步优化。可见w直接决定了每个元素在误差最小化中的话语权——这正是理解weight[j]为何需要修正的关键。
二、核心回答:这是一次经验修正,而非科学推导
针对"为什么不用weight[j] = qw[j]",作者 ikawrakow 在讨论中的回答非常坦诚:这只是经验性修正,背后没有严格的科学依据("this is simply an empirical correction, there is no science behind it")。
其来龙去脉是:
- imatrix 出现之前,量化器用不带外部信息的权重做加权 RMSE 最小化,实践反复证明:给幅度更大的权重更高的重要性,量化结果更好。于是人们实验过各种幅度依赖形式——
x²、|x|、σ² + x²、σ + |x|等——都是被尝试过的变体; - imatrix 出现之后,作者的期望自然是"终于可以扔掉这些非科学的东西,直接使用 Hessian 对角元素"。但实践证明事情没那么简单:单纯使用 Hessian 对角线,效果并不理想;
- 把
sqrt(σ² + x²)保留在qw[j]旁边,确实能提升量化精度(以 perplexity 或 KL 散度衡量)。
换言之,sqrtf(sigma2 + xb[j]*xb[j])是在"有了 Hessian 之后依然保留的幅度依赖修正项",用来弥补纯 imatrix 方案的不足。
三、为什么偏偏是sqrt(sigma² + x²),而不是别的形式
作者给出了三条理由,逐条对应设计约束:
| 约束 | 含义 | 对公式形式的推导影响 |
|---|---|---|
| Hessian 已提供大量重要性信息 | 经验修正不能再像无 imatrix 时代那样强烈依赖幅度 | 幅度依赖的指数不能太高,sqrt恰好弱化了x²的增长 |
| 小幅度权重的相对重要性不能趋近于零 | 纯x²会让小权重的重要性趋近 0,从而几乎不参与误差最小化 | 必须有一个"地板"项σ²兜底 |
| 无 imatrix 时代的经验积累 | 幂次过强/过弱都试过 | sqrt(σ² + x²)是作者尝试过的所有形式中效果最好的 |
从数学形态看,sqrt(σ² + x²)的渐近行为非常合理:当|x| >> σ时,它近似于|x|(线性幅度依赖);当|x| << σ时,它趋近于常数σ(小权重获得平坦的非零重要性)。它既保留了"大幅度元素更受重视"的经验规律,又不会让小权重被完全忽略,还不会像x²那样过度放大离群值的影响——这与"修正不能太强地依赖幅度"的第一条约束是自洽的。
四、为什么需要修正 Hessian 本身
即使接受了"要对 imatrix 做幅度修正",还需要回答:为什么 Hessian 信息本身需要修正?作者的解释分两层:
- 只用对角线本身就是近似。我们使用的是完整 Hessian 的仅对角近似(每个权重只保留二阶导数
∂²L/∂w²,忽略交叉项)。在作者的工程经验里,"给近似加上一个修正项"往往比"直接信任近似"效果更好; - RMSE 未必是正确的相似度度量。加权 RMSE 只是"量化前后权重向量差异"的便利度量(表达式简单、可解析求解),但真实的目标可能是让量化模型的输出分布(perplexity、KL 散度)最接近原模型。换一种相似度度量,就会对应一个不同的"理论 Hessian"、不同的重要性矩阵。既然我们并不确切知道该最小化什么,那么当前使用的 importances 本质上仍然是经验实验的结果。
这解释了为什么代码中还会出现fudge因子——例如 ggml/src/ggml-quants.c 的const float fudge = ggml_get_quantize_fudge_factor(GGML_TYPE_Q4_0),其取值表定义在 ggml/src/ggml.c。这些都是"工程修正"哲学的一部分:理论是起点,实践调优才是终点。
五、正则化视角:为什么经典正则化在量化中行不通
讨论中另一个有价值的分支是"能否用正则化理论解释/替代这个经验修正"。作者的观点非常鲜明:
- Tikhonov 正则化恰恰是量化最不想要的。Tikhonov 项的目标是把解压向零(Gaussian prior centered at zero),而量化时我们绝不想把量化值做得尽可能小;
- 有人提出"对权重的 log 做正则化"(等价于 log-normal prior、以 1 为中心的 Gaussian),把权重正则向 1(即全部等权)。参与者 jukofyork 实测确认:这类更"有理论根基"的方案在当时的模型上表现比作者的经验公式略差;
- 作者进一步指出,i-quants 在某种意义上已经自带"正则化":它强制一组量化值落在有限网格点上(例如
IQ2_XXS在 E8 格上只用 6561 个候选点中的 256 个),这种离散化天然抑制过拟合,可以视为一种正则化形式。
结论是:LLM 量化并不是经典意义上"需要往目标函数里加惩罚项"的问题,经验修正之所以胜出,是因为它的形式恰好与量化误差的实际分布匹配。
六、MoE 场景:imatrix 数据质量与专家激活问题
讨论的后半段把话题延伸到 MoE 模型(DeepSeek、Arctic 等)的 imatrix 计算,并揭示了一个与weight[j]密切相关但常被忽视的问题:如果某个专家在训练数据上从未被激活,那么它的 Hessian 对角元素(即qw[j])就没有数据积累。
6.1 常见的三个数据质量问题
参与者 jukofyork 归纳了社区常见的三个误区:
- (A) 用同一份 ~250KB 的
calibration_datav3.txt同时跑密集模型和 DeepSeek-V3。对 256 专家的 MoE,有效样本量最多只有密集模型的 1/32(约 3%);若路由惩罚(router penalty)训练不佳,实际更低。应增大样本量,或在大模型无法增大样本时调整正则化因子; - (B) 用
wiki.train.raw算 imatrix、再用wiki.test.raw测 perplexity。两者文本风格高度相似(同一来源的 wiki 文章),会得到"看起来很美"但实际上有偏的 imatrix 改进估计; - (C) 只对每个超长上下文的前 512 个 token 跑 imatrix。会偏向短序列的激活模式,且 transformer 块与 MLP 块的张量被采样的机会不均等。
6.2 专家未被激活时的真实报错
社区在 Arctic(128 专家)上实测发现:blk.0.ffn_gate_exps.weight的 imatrix 数据只有 99.22% 覆盖率,导致保存时被跳过:
save_imatrix: entry 'blk.0.ffn_gate_exps.weight' has partial data (99.22%) - skipping进而在极低比特量化时直接失败:
llama_model_quantize: failed to quantize: Missing importance matrix for tensor blk.0.ffn_gate_exps.weight in a very low-bit quantization从源码看,imatrix 收集器对 MoE 张量确实会"按专家维度"分别统计:在 examples/imatrix/imatrix.cpp 中,collect_imatrix针对GGML_OP_MUL_MAT_ID/GGML_OP_MOE_FUSED_UP_GATE读取ids张量(top-k 选中的专家 id),循环遍历n_as个候选专家,只对当前 batch 中实际被路由到的专家累加其输入激活的平方和。因此某专家长期不被路由,其对应列就没有统计量——这与"整个 experts 张量因一个专家缺失而无法量化"的社区报告完全吻合。
6.3 用--override-kv提高激活专家数
作者给出的实用建议是:用--override-kv让模型激活比元数据规定更多的专家,从而提高 imatrix 对每个专家的覆盖率:
./bin/llama-imatrix -m some_model -f some_training --override-kv deepseek2.expert_used_count=int:N--override-kv的解析位于 common/common.cpp,格式为KEY=TYPE:VALUE,支持int、float、bool、str四种类型(见 docs/parameters.md)。注意expert_used_count的 key 名称随架构而变,例如qwen3moe.expert_used_count(参见 github-data/discussions/385)、llama4.expert_used_count(参见 github-data/pull_requests/321 - LlaMA-4 supporttext only.md)等。
作者的实测经验(以 DeepSeek-Lite 为例,元数据为 6 个激活专家):
- 激活 7 个专家:PPL 略降约 0.2%;
- 激活 8、9 个:与默认基本持平;
- 激活 10 个:PPL 升高约 0.3%。
结论是:在低 bpw 量化(如IQ1_S、IQ1_M)下,略微调高激活专家数(如 DeepSeek-Lite 从 6 提到 8、Mixtral 8x7 从 2 提到 3)能明显改善效果;但随着 bpw 升高,收益消失甚至适得其反(Mixtral 在 4+ bpw 下调专家数已无意义)。社区对 R1 的测试也印证了这一点(下述表格为社区用户实测数据,仅作现象展示,不代表本仓库结论):
| 激活专家数 | PPL(同一 imatrix 下多组上下文位置) |
|---|---|
| 8 | 3.4155, 4.2311, 3.0817, 2.8601, 2.6933, 2.5792, 2.5123, 2.5239 |
| 6 | 3.4227, 4.2400, 3.1610, 2.9933, 2.8307, 2.7110, 2.6253, 2.6488 |
| 4 | 3.5790, 4.5984, 3.5135, 3.4490, 3.2952, 3.2563, 3.1883, 3.2978 |
| 2 | 6.2387, 7.7455 |
同时要注意:DeepSeek-V3 系列用 sigmoid 而非 softmax 做门控,激活更多专家时输出隐状态之和会持续增大,极端情况下(如 10/12 专家)可能出现 NaN,而 16 专家反而正常——参与者推测与各专家输出的相关性、误差相互抵消有关。这提醒我们:expert_used_count是用于改善 imatrix 质量的校准手段,不代表推理时也应使用同样的专家数。
七、总结:一行公式背后的三层设计思想
回到最初的问题——"为什么不是weight[j] = qw[j]",可以从三个层次总结:
- 经验层:无 imatrix 时代的量化实践已经证明,幅度更大的权重应当获得更高重要性;
sqrt(σ² + x²)是这一规律的最优经验形态; - 互补层:imatrix 提供的是 Hessian 对角近似,它"信息量大但未必完备";加上幅度修正项后,两者互补,实测 perplexity/KL 更优;
- 务实层:作者明确表示这是"带 fudge factor 的工程实践"——理论是起点,实验调优才是终点。同一哲学也体现在 MoE imatrix 场景:与其纠结正则化理论,不如用
--override-kv expert_used_count提高专家激活率、用更贴合的校准数据,把qw[j]本身的质量做扎实。
理解这行代码,等于同时理解了 ik_llama.cpp 量化体系的两大支柱:重要性矩阵的采集(examples/imatrix/imatrix.cpp、examples/imatrix/README.md)与加权 RMSE 的求解(ggml/src/ggml-quants.c、ggml/src/iqk/iqk_quantize.cpp)。对于希望自行改进量化精度的开发者,这篇文章给出的关键启示是:公式形态只是表象,校准数据质量、专家覆盖率与幅度修正三者共同决定最终量化质量。
【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考