量化方法论实证:为什么 LQER/L²QER 难以改进 ik_llama.cpp 的 k-quants 与 i-quants
【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
导读
本文基于 ik_llama.cpp 仓库维护者 ikawrakow 在仓库讨论区发布的原始技术论证(见 github-data/discussions/15),完整梳理其关于 LQER/L²QER 低秩量化方法能否改进 k-quants 与 i-quants 的预测、PPL(困惑度)对比实验方法、SVD(奇异值分解)量化探索历程,以及围绕 imatrix 文件格式演进的技术争论。读者读完本文后,将掌握:如何公平地跨工具链比较量化 PPL、k/i-quants 的网格表是如何从 E8/D4 晶格统计生成的、低秩分解在 LLM 量化中失效的根因,以及 imatrix.dat 与 GGUF 两种格式各自的工程权衡。
一、背景:LQER/L²QER 是什么,为什么引发讨论
LQER/L²QER 是 2024 年初在 arXiv 上发布的一类基于低秩分解的 LLM 量化方法(论文编号 2402.02446),核心思路是:对权重矩阵做低秩近似,仅对"减去低秩分量后剩下的残差"做量化,从而以较低位宽获得更小的量化误差。由于论文宣称的结果接近"SOTA",llama.cpp社区随即出现要求用其改进既有量化方法的讨论,甚至有人提交了支持用 Numpy 做 SVD 的 PR(以便计算全模型与量化模型差值的奇异值分解)。
ikawrakow 在 2024-08-09 的讨论中,基于其对 k-quants 与 i-quants 数年的开发经验,给出了一个鲜明的公开预测:
LQER/L²QER 将无助于改进
llama.cpp中任何 k-quants 或 i-quants。
这一预测并非空谈。ikawrakow 记得大量模型(尤其是 LLaMA-v1 与 LLaMA-v2 这些早期模型)的 PPL 数值,而这些恰好是 LQER 论文 Table 3 中用来对比的模型——他一眼看出论文结果远非宣传的 SOTA 水平。讨论的后续发展(包括作者自己在ik/try_svd分支上的 SVD 实验、compilade 的低秩适配器实验)也印证了这一判断。
二、核心实验:跨工具链公平对比 L²QER 与 k/i-quants
2.1 如何让 llama.cpp 的 PPL 与 Python 工具链可比
论文中的 PPL 用标准 Python 工具计算,而llama.cpp的 PPL 与之往往差异显著。但有一个关键性质:量化模型与 f16 模型的 PPL 比值(或差值 ∆PPL)几乎与 PPL 的绝对计算方式无关。LQER 论文正是用PPL(Q) - PPL(f16)作为指标,因此该指标在两种工具链间是可比的。
为了让比较更严谨,ikawrakow 分析了两种计算方式的差异:
- 采样方式:
llama.cpp顺序遍历评测文本,Python 则随机选取给定上下文长度的样本。他认为这不构成实质差异(不超出 PPL 估计的统计不确定性),故未修改。 - 平均区间:
llama.cpp仅对上下文窗口n_ctx的后半段求平均对数概率,Python 则对整个上下文求平均。llama.cpp的做法一阶近似于报告3/4 n_ctx的 PPL,Python 估计的则是1/2 n_ctx的 PPL。ikawrakow 将该值从first = n_ctx/2调整为first = std::max(1, n_ctx/128),使结果与 LQER 论文(上下文 2048)报告的 f16 PPL 最接近。
在当前仓库的 examples/perplexity/perplexity.cpp 中可以看到这段 PPL 求值逻辑的当代形态:const int first = n_ctx/2;后调用process_logits(...)处理n_ctx - 1 - first个 token 的 logits,PPL 定义为负对数似然的指数均值(见 examples/perplexity/perplexity.cpp)。当前仓库还支持通过--kl-divergence-base记录 f16 logits 文件,计算量化模型与 f16 模型的 KL 散度、PPL 比等更多统计量(见 examples/perplexity/README.md),可用于复现类似分析。
2.2 f16 基准 PPL
按上述修改后,四个模型的 f16 PPL(带标准差)如下:
| 指标 | LLaMA-v1-7B | LLaMA-v1-13B | LLaMA-v2-7B | LLaMA-v2-13B |
|---|---|---|---|---|
| f16 PPL | 5.6291 ± 0.02202 | 5.0172 ± 0.01893 | 5.4802 ± 0.02128 | 4.8706 ± 0.01824 |
2.3 ∆PPL 对比结果
L²QER 论文的量化位宽为 4.3 bpw,与IQ3_XS(4.25 bpw)、Q4_K_S/IQ4_K(4.5 bpw)处于同一量级;IQ3_K(3.4 bpw)则用于参照。各方法相对 f16 的 ∆PPL 如下:
| 量化方法 | bpw | LLaMA-v1-7B | LLaMA-v1-13B | LLaMA-v2-7B | LLaMA-v2-13B |
|---|---|---|---|---|---|
| L²QER | 4.30 | 0.220 | 0.100 | 0.100 | 0.060 |
| IQ3_K | 3.43 | 0.220 | 0.142 | 0.114 | 0.144 |
| IQ4_XS | 4.25 | 0.075 | 0.054 | 0.065 | 0.048 |
| Q4_K_S | 4.50 | 0.065 | 0.041 | 0.063 | 0.044 |
| IQ4_K | 4.50 | 0.041 | 0.033 | 0.043 | 0.034 |
结论一目了然:L²QER 在 4.3 bpw 下,即使面对更低位宽的IQ3_K(3.43 bpw)也占不到明显优势,而面对同量级或略高 bpw 的IQ4_XS、Q4_K_S、IQ4_K则全面落败。i-quants 中新增的IQ4_K在四个模型上取得了全部最低的 ∆PPL。
这些量化类型在当前仓库中均有对应定义:IQ4_XS对应LLAMA_FTYPE_MOSTLY_IQ4_XS = 30,IQ2_K/IQ3_K/IQ4_K分别对应 ftype 138/139/140(见 include/llama.h),并在 src/llama-quantize.cpp 的量化合法性检查与 K-quants 回退逻辑(例如 ftype 为 IQ3_S/IQ4_XS 等时回退到IQ4_K)中得到完整支持。
三、SVD 探索史:为何低秩分解对 LLM 量化不奏效
3.1 作者的第一手尝试
ikawrakow 早在 2023 年 4 月(刚接触llama.cpp并开始思考 LLM 量化时)就尝试过 SVD 路线——这是 ML 实践者的标准工具箱之一。结果令人失望:需要太多奇异值项才能与块式量化(block-wise quantization)竞争,而那时他已经开始做 k-quants 了。
他给出的深层解释是:若量化本身质量较差,用 SVD 的前几个分量确实可能带来改善;但若量化质量已经不错,在扣掉前几个奇异值分量后,剩余量化误差仍然比用同样比特数换来的更好量化方案大 2X–5X,仅靠少数 SVD 项无法补救。
他最先尝试的正是论文未做过的方案——先对权重做低秩分解,再量化剩余部分,同时让低秩适配器(类似 LoRA)去补偿量化误差。若成功,不仅能压缩模型,低秩分解的矩阵乘法远快于全矩阵,还能带来大幅性能提升。然而结果只有 LLaMA-1 早期层的K/Q张量取得中等程度成功,其余位置在逼近"全 SVD"之前都毫无希望。
3.2 try_svd 分支的实证
2024-08-27,ikawrakow 公开了ik/try_svd分支上的探索代码:他"误用"quantize-stats工具观察RMSE(均方根误差)随 SVD 分量数的变化,支持 SVD 在量化前或量化后两种路径。代码并非生产级(仅 AVX2 向量化、简单多线程),但足以说明:当量化工作良好时,SVD 对 LLM 量化没有增量价值——因为全 SVD 能把 RMSE 归零,说明实现本身是正确的。
当前仓库的 examples/quantize-stats/quantize-stats.cpp 仍保留着类似的 RMSE 统计逻辑(rmse = sqrt(stats.total_error / num_samples)并输出 maxerr、95 分位等指标),可作为复现此类误差分析的工具基础。
compilade 在回复中进一步解读:当SVD_BEFORE为 false 时,try_svd的输入非零,SVD 实际作用于"输入与输出之差"(见讨论中引用的 quantize-stats.cpp 第 317 行附近),这与 LQER 的做法类似(同时量化低秩张量),因此即便未同时测试"量化前 SVD + 量化后 SVD"的组合,该分支也足以作为概念验证:朴素 LQER 确实不如更好的量化方案。
3.3 IQ 网格表的生成方式与"难以复现"之辩
compilade 提到尚未为大多数 IQ 类型实现 Numpy 反量化(仅IQ4_NL和IQ4_XS有),原因是"其余类型的网格有点大"。ikawrakow 回应了这一质疑,并揭示了 IQ 表的生产流程:
- 他用完整的 E8 或 D4 晶格量化一批模型,统计每个晶格点的使用频次——这些统计数据比最终 IQ 表大数个数量级,且生成耗时很长;
- 随后运行一个优化,目标是 a) 最大化被选晶格点的使用计数,b) 最小化未被选晶格点到最近被选晶格点的最大(或计数平均)距离;
- 该优化代码从未公开,且运行时长远超每次调用可承受的范围(晶格点使用统计也比表本身大得多),因此无法在运行时动态生成;
- 至于"表太大"的说法,作者反问道:这些数据能放进 L1 缓存,莫非我们跑在 16 kB 内存的机器上?
从当前仓库源码可见这些表确实以静态数据形式内嵌:例如 ggml/src/ggml-quants.c 中通过iq2xs_grid + (x[i].qs[...] & 511)查找IQ2_XS网格点,iq2xs_grid即为静态查表;AVX2/NEON 路径(如 ggml/src/ggml-quants.c)也直接以 64 位整数加载网格条目。这印证了"表由离线大规模统计优化生成、运行时直接查表"的设计。
3.4 低秩适配器路线的验证
compilade 在 OpenELM-270M 小模型上测试了"纯Q2_K+ F16 LoRA(rank 32)"方案:相比朴素 LQER 确实有改进,但仍不如默认的Q2_K混合方案(后者部分层使用Q3_K)。ikawrakow 的回应点明了本质的比特预算之争:
用足够多的主成分当然最终会带来改进,但问题是:把这多出来的比特花在别处——换一种量化、使用量化混合等——是否能获得同样的改进?
他同时指出,Q2_K在"量化精度与模型大小的折中"上远非最优(IQ2_S以及本仓库的IQ2_K都证明了这一点),这为 i-quants 的后续迭代留下了空间。事实上,ik_llama.cpp 后来正是沿着"更优的 IQ 网格 + 量化混合"路线持续演进:如IQ3_K、IQ4_K及后来的 R4 变体(GGML_TYPE_IQ2_K_R4/IQ3_K_R4/IQ4_K_R4,见 ggml/src/ggml-quants.c 与 include/llama.h 的 ftype 338/339/340)。
四、围绕 imatrix 格式的技术争论与演进
讨论的后半段从"LQER 是否有用"自然延伸到了imatrix(激活重要性矩阵)文件格式:compilade 推动将 imatrix 改为 GGUF 格式(对应llama.cppPR-9400),而 ikawrakow 强烈反对,因为这会破坏他大量轻量独立工具的工作流。
4.1 两种格式的优劣清单
compilade 在 2024-09-13 的回复中系统列出了双方论点:
保留简单imatrix.dat格式的优势
- 解析更简单:仅约 20 行代码(含恰当错误处理约 50 行);
- 加载无需链接
ggml; - 允许编写自包含的小程序做量化实验(ikawrakow 的典型工作流:
g++ -O3 some_tool.cpp && ./a.out some_imatrix,无需 Makefile/CMakeLists)。
改用 GGUF 的优势
- 减少专用格式数量,与现有及未来的 GGUF 工具链互通(
gguf_dump.py、HF 预览、未来的gguf-diff); - 可扩展性:可添加更多元数据、元数据类型变更更简单(如 int32 与 int64);
- 计数是多维的:堆叠的 MoE 张量中每个专家拥有独立的激活计数,且能完整保留求和值——这对合并 imatrix 文件(
--in-file)至关重要。
保留imatrix.dat的劣势
- 无文件头魔数,不易识别;
- MoE 激活求和的序列化方式怪异(整个张量共享同一 chunk 计数);
- 向后兼容扩展困难。
改用 GGUF 的劣势
- 依赖更多代码来读写,增加出错概率(虽然该代码与模型加载共享,修复 bug 惠及所有用途);
- 无法再编写独立实验程序,必须链接
libggml.so(ikawrakow 实测其构建的libggml.so约 370 MB,见讨论中的ls -al输出),或引入 header-only 的 gguf 加载器。
4.2 ikawrakow 的立场与替代方案
ikawrakow 的核心诉求是保持 imatrix 的"平凡可解析"特性。他演示了一种无需 GGUF 的向后兼容扩展方案:读取条目数时若遇到std::numeric_limits<int>::max()哨兵值,即判定为"扩展 imatrix",再读取真实条目数与版本信息——从而所有旧 imatrix 继续可用,扩展可以加在任何位置(不限于文件末尾),且无需链接 370 MB 的libggml.so。
关于"合并不同上下文长度生成的 imatrix",他指出自己的约定是在文件名中携带上下文长度(例如some_imatrix_c${context}.out),并强调上下文长度对量化结果的影响小得出奇;而 compilade 则认为 GGUF 格式按 token(激活)数而非 chunk 数存储计数,才是既不关心 batch 大小又能正确合并的方式。
4.3 最终结局
双方最终达成务实妥协:llama-imatrix在输出文件名以.gguf结尾时采用 GGUF 格式(默认),否则沿用旧格式;llama-quantize两种格式都能读取;同时支持反向转换:
$ ./bin/llama-imatrix --in-file imatrix.gguf -o imatrix.dat不过 GGUF 转回.dat会丢失 3D 张量评估计数的部分形状信息(影响堆叠 MoE 张量的部分数据处理),反之亦然。到 2025-07-12 的最终回复中,ikawrakow 表示已基本不再使用主线的llama.cpp,imatrix 的"GG 化"不再影响其需求,并认可了这次格式演进。当前仓库的 examples/imatrix/imatrix.cpp 使用说明中即可看到--in-file、--chunk、--output(-o)等参数,与上述讨论的接口形态一致。
五、结论与启示
这场讨论给出了几条至今仍具指导意义的工程结论:
- 量化方法的评估必须以公平的比特预算为前提:L²QER 宣称的 SOTA 结果在跨工具链校准后的 ∆PPL 对比中,不敌同等乃至更低 bpw 的
IQ4_XS、Q4_K_S、IQ4_K。llama.cpp生态的 PPL 与 Python 工具链差异较大,但"量化 PPL 相对 f16 PPL 的差/比"这一指标高度可比,是跨工具对比的正确锚点。 - 低秩分解的收益取决于基座量化质量:当块式量化本身足够好时,残差误差远大于可被少数 SVD 项吸收的量级(2X–5X),低秩适配器路线在小模型上也仅与朴素 LQER 相当、仍不及默认量化混合。
- i-quants 的竞争力来自离线大规模晶格统计优化:E8/D4 晶格点使用频次的统计与"最大化使用、最小化距离"的选点优化,产生了远超手工设计的网格表,且静态查表(如
iq2xs_grid)在运行时几乎零开销——这正是 k/i-quants 无需"花哨"后处理也能保持领先的原因。 - 文件格式之争的本质是工作流与互操作性的权衡:20 行的自描述格式与通用可扩展格式各有拥趸,最终以"按扩展名分流、双向转换、双格式可读"的工程妥协收场,并让 imatrix 数据得以与 GGUF 工具链互通。
对于想深入了解的读者,建议进一步阅读 github-data/discussions/15 的原始讨论全文、include/llama.h 中的量化 ftype 枚举、src/llama-quantize.cpp 中的量化调度与回退逻辑、ggml/src/ggml-quants.c 中的网格查表实现,以及 examples/quantize-stats/quantize-stats.cpp 的误差统计工具。
【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考