《GGUF 量化内幕:块量化、K-quants、码本与三值量化》全文目录


第一部分 背景与基础

第 1 章 为什么需要 GGUF 量化

本章建立全书的问题框架:端侧部署的硬约束是什么,量化在 llama.cpp 生态中扮演什么角色,以及为什么需要"多种格式并存"而不是一个万能格式。

1.1 端侧部署的"体积---精度---速度"三角

1.1.1 内存带宽作为端侧解码的首要瓶颈

本节用 roofline 模型说明自回归解码的算术强度极低,实际 tok/s 主要由权重从内存读入的速度决定,而非算力。应给出典型移动 SoC 的带宽量级与参数量---可达速度的对应关系,为后续所有格式的"每权重字节数"讨论建立物理直觉。

1.1.2 存储占用与冷启动延迟

本节区分"文件体积"和"运行时 RSS"两个指标,说明 mmap 加载下冷启动受首次缺页和页缓存状态影响,并用端侧实测数据展示 Q4_K_M 与 FP16 在模型体积、RAM 占用、tok/s 和冷启动上的量级差异------例如 3B 模型 Q4_K_M 约 1.7 GB、2.1 GB RAM、11.2 tok/s,而 FP16 为 6.0 GB、7.2 GB、2.1 tok/s。

1.1.3 为什么单一格式无法同时满足所有场景

本节论证"一个格式打天下"的不可能性:手机端需要 <2 bpw 的极限压缩,工作站可以承受 Q5_K 以上的质量开销,而调试和参考比对需要 Q8_0 甚至 F16。GGUF 的答案不是折中,而是让每个张量独立选择格式。

1.2 量化术语的最小共识

1.2.1 块、超级块与 bit/weight 的定义

本节严格定义 block(共享一个尺度的权重集合)、super-block(K-quants 的 256 值层次化单元)和 bpw 的计算方式,强调 bpw 是"文件字节数 × 8 / 权重数",而非"量化码位宽",因为尺度、最小值、码本索引都计入开销。

1.2.2 尺度、最小值偏移与零点

本节区分三种参数化方式:对称块量化(只有尺度,如 Q4_0)、非对称块量化(尺度 + 最小值,如 Q4_K 的子块)、以及显式零点(如 Q4_1)。说明三者在表示能力和字节开销上的差异。

1.2.3 对称、非对称与"伪对称"

本节澄清一个常见混淆:Q4_0 的 q-8 是存储偏移而非真正的最小值,不增加参数开销;而 Q4_K 的 d_min * m_j 是逐子块学习出来的真实偏移,需要额外字节。这一区别决定了后续格式的误差结构。

1.3 GGUF 在 llama.cpp 工具链中的位置

1.3.1 转换、量化、mmap 加载、推理的四阶段流水线

本节给出从 Hugging Face 权重到端侧推理的完整链路,标出 convert_hf_to_gguf.py、llama-quantize、llama-imatrix 和运行时加载各自负责的决策点,为第 15 章的工具链细节预留入口。

1.3.2 GGUF 与 safetensors、PyTorch 的分工边界

本节说明 GGUF 不是训练格式,也不是通用交换格式,它的设计目标是在 mmap 友好、逐张量类型分派和端侧推理三项约束下做最优,因此它的"奇怪之处"(POD 结构体、固定对齐、type id 硬编码)都是这些约束的产物。

1.4 量化范式的三次跃迁

1.4.1 第一范式:块共享尺度的仿射量化(Q4_0/Q8_0)
1.4.2 第二范式:超级块内的层次化尺度与最小值(K-quants)
1.4.3 第三范式:重要性加权码本与三值结构化稀疏(IQ/STQ1/PTQ1)

这三节以递进方式建立全书主线:第一范式用单一尺度换简单性,第二范式用两级尺度换质量,第三范式用码本或结构化约束换极限位宽。每一范式都应指出其"解决了前一范式的什么失效",而不是并列罗列格式。

第 2 章 数值基础与量化误差

本章提供贯穿全书的分析工具:如何衡量一个量化格式"坏在哪里",以及为什么不同类型的格式需要不同的评价指标。

2.1 浮点与定点表示的取舍

2.1.1 fp16、bf16、fp32 的动态范围与精度

本节对比三者在表示权重和尺度时的有效位数,说明为什么 GGUF 块尺度普遍用 fp16 而非 bf16(尺度值域窄,fp16 的 10 位尾数足够),以及 fp32 仅在 F32 张量和部分参考实现中出现。

2.1.2 尺度的量化误差如何传播到权重

本节推导 \(y = d \cdot q\) 中 \(d\) 的舍入误差对重建误差的贡献,说明当 \(d\) 以 fp16 存储时,相对误差约 \(2^{-11}\),在 4-bit 量化噪声面前通常可忽略,但在 1--2 bpw 的码本格式中会成为不可忽视的底噪。

2.2 仿射量化的通式与误差界

2.2.1 量化:\(q = \mathrm{round}((x - z)/s)\) 与夹取

本节给出对称和非对称两种参数化下的量化公式,强调夹取(clamp)到码字范围是误差的非线性来源,离群值被夹取后产生的是"饱和误差"而非舍入误差。

2.2.2 反量化:\(x = s(q - z)\) 与重建误差分解

本节将重建误差分解为舍入误差(由码位宽决定)和尺度失配误差(由块内分布异质性决定),说明 Q4_0 的误差主要由后者主导------这正是 K-quants 要解决的问题。

2.2.3 块内分布异质性对单一尺度格式的惩罚

本节用一个构造性例子展示:同一 32 值块中混入一个大离群值和多个接近零的值时,最优单一尺度被迫由大值主导,小值全部量化为零。该例子将作为第 4 章 Q4_0 实验的理论预演。

2.3 重要性加权与码本量化的误差视角

2.3.1 加权最小二乘:\(\min \sum_i \alpha_i (x_i - \hat{x}_i)^2\)

本节引入 imatrix 的数学形式,说明当 \(\alpha_i\) 反映激活幅度时,量化器应把误差预算向"对输出影响大"的权重倾斜,而不是均匀最小化 MSE。

2.3.2 码本量化的误差结构:索引误差而非尺度误差

本节指出码本格式(IQ 系列)的误差来源与仿射量化不同:每个值是查表得到的离散向量,误差由"最近邻选择"决定,不存在连续的尺度失配项,但存在码本覆盖不足导致的系统性偏差。

2.4 三值量化的特殊误差结构

2.4.1 阈值效应:从"劣化"到"任务崩溃"

本节说明三值格式的 PPL 劣化不是 K-quants 式的平滑曲线,而是在某个位宽/模型组合下出现任务能力的断崖式下降,因此不能用单一 PPL 数字外推"再压一点还能用"。

2.4.2 为什么三值格式必须逐模型验证

本节论证三值量化的可用性高度依赖模型本身的冗余度和训练时的三值感知程度(如 BitNet 系模型与事后三值化的差异),为第 13 章的适用边界提供方法论。

2.5 评价指标:从逐块误差到端到端任务

2.5.1 MSE、最大相对误差与逐块可视化

本节说明这三个指标是"格式调试"工具,用于定位解包错误或尺度估计失败,而不是质量判据。

2.5.2 PPL、KL 散度与任务准确率

本节区分 PPL(对整体分布敏感)、KL(对 logits 形状敏感)和任务指标(对特定能力敏感),说明为什么 IQ 系列常用 KL 与 base 模型对比,而 K-quants 常用 PPL 劣化百分比。

2.5.3 端侧必须额外测量的指标:TTFT、冷启动、P95 延迟

本节指出端侧部署的质量不只是 PPL,首 token 延迟、冷启动时间和延迟方差同样决定可用性,并用 Android 实测中不同芯片组 P95 延迟方差从 ±12% 到 ±52% 的差异说明后端覆盖的影响。

第 3 章 GGUF 容器与张量类型

本章从"文件里到底存了什么"入手,建立读者对 GGUF 的字节级直觉,并给出用 gguf-py 操作文件的完整 API 路径。

3.1 GGUF 的二进制布局

3.1.1 header、tensor_infos\[\]、alignment 与 tensor data blob

本节逐段解释 GGUF 的顺序结构:magic、版本、张量数、KV 数,随后是每个张量的名称、维度、ggml_type 和偏移,最后是对齐后的原始数据块。强调 tensor_infos 中的 offset 是相对于数据块起点的偏移,而非文件绝对偏移。

3.1.2 general.alignment 与 mmap 加载的零拷贝条件

本节说明默认 32 字节对齐如何保证每个张量数据块可以安全地映射为对齐指针,从而使 llama.cpp 能够直接在 mmap 区域上解释 block_q4_K 等 POD 结构体,避免加载时的整块拷贝。

3.1.3 为什么量化块结构体必须是 POD 且无填充

本节指出 mmap 加载要求 block_* 结构体的内存布局与磁盘字节完全一致,任何编译器填充或非平凡成员都会破坏"磁盘字节即结构体"的契约。这是新增格式时最容易出错的地方之一。

3.2 ggml_type:量化决策的下放

3.2.1 每个张量独立的类型字段

本节论证 GGUF 的量化不是文件级属性:同一文件内可以同时存在 Q4_K、Q6_K 和 F32,读取者根据每个张量的 type 分派解码路径。Q4_K_M 是这种机制的产品化表达,不是一种编码格式。

3.2.2 类型分派表:type_traits 与 to_float/from_float

本节介绍 ggml.c 中的 type_traits 数组如何把 type id 映射到块大小、类型大小和反量化函数指针,说明新增格式必须同时更新这个分派表。

3.2.3 混合类型的实际分布:以 Q4_K_M 为例

本节给出一个真实的逐张量类型统计:注意力 QKV 和 FFN 主体多为 Q4_K,输出嵌入层为 Q6_K,归一化和部分嵌入为 F32,用数据证明"_M 文件内部是混合的"。

3.3 用 gguf-py 读写 GGUF

3.3.1 安装与核心对象:GGUFReader、GGUFWriter、dequantize

本节给出 pip install gguf 与源码 editable 安装两条路径,并列出 ReaderField、ReaderTensor 的关键属性和 contents()、get_field() 的用法。

3.3.2 读取元数据与遍历张量类型分布

本节提供一个可直接运行的脚本,打印 general.architecture、general.file_type 等关键字段,并用 Counter 统计张量类型分布,让读者亲手确认混合精度事实。

3.3.3 反量化单个张量并对接 numpy/PyTorch

本节展示 gguf.dequantize(tensor.data, tensor.tensor_type) 返回 float32 数组,并说明反量化是只读操作、量化不可无损往返。

3.3.4 用 GGUFWriter 写一个最小 GGUF

本节复现官方 writer.py 的三步写文件流程(header → kv data → tensor data),并解释 add_custom_alignment 如何影响数据块起始偏移。

3.3.5 原地修改元数据的边界

本节说明 'r+' 模式只能安全修改定长标量字段,变长字段(词表、字符串数组)必须走复制式工具,否则会破坏后续偏移。

3.4 type id 的兼容性风险

3.4.1 主线 type id 与 fork/PR 自定义 id 的冲突
3.4.2 STQ1_0 与 PTQ1_0 的 id 归属问题

这两节讨论一个生态层面的现实:PTQ1_0 使用 type id 143、STQ1_0 的 id 在不同文档中有 42/43 的差异,且部分格式来自特定 fork 或未合并 PR。读者必须理解"GGUF 格式可读"不等于"所有 llama.cpp 构建都能正确分派"。

第二部分 简单块量化

第 4 章 Q4_0:块量化的起点

本章以 Q4_0 为最小可运行示例,建立"结构体---公式---循环---误差"的完整分析模板,后续所有格式都复用这一模板。

4.1 字节布局与结构体

4.1.1 18 字节/32 值:d: f16 + qs[16]

本节给出 Q4_0 块的精确字节图,说明 16 个打包字节如何承载 32 个 4-bit 码,以及 sizeof(block_q4_0) == 18 的静态断言意义。

4.1.2 block_q4_0 的 POD 约束

本节重申结构体必须与磁盘布局一一对应,不允许填充或对齐修饰。

4.2 公式与反量化循环

4.2.1 \(y_i = d \cdot (q_i - 8)\),\(q_i \in 0,15\)

本节推导该公式的两种等价视角:无符号码减去 8 得到对称有符号量,或视为零点固定为 8 的非对称量化。强调减 8 不消耗额外存储。

4.2.2 低半字节与高半字节的交错解码

本节走读 16 字节打包码如何映射到 32 个输出位置,给出"前 16 个取低半字节、后 16 个取高半字节"的循环,并说明不同 SIMD 实现可能采用不同的解包顺序但数学等价。

4.3 量化端:尺度选择与舍入

4.3.1 块最大绝对值与尺度估计

本节说明 Q4_0 的尺度通常取块内最大绝对值的函数(如 max / 7 或类似),使有符号码覆盖 \(-8, 7\) 或对称范围,并讨论不同估计准则对离群值的敏感度。

4.3.2 四舍五入与夹取

本节说明 round 与 clamp 的先后顺序,以及夹取产生的饱和误差在块内分布不均时的表现。

4.4 误差实验与局限

4.4.1 块内异质性对 Q4_0 的惩罚

本节用构造数据和真实权重切片展示:当一个 32 值块中同时存在大值和近零值时,小值被量化到同一码字,重建误差集中在低幅度区域。

4.4.2 Q4_0 与 Q4_K 在相同 bpw 下的对比预告

本节给出 Q4_0 ≈ 4.5 bpw 与 Q4_K ≈ 4.5 bpw 的 PPL 对照(Q4_0 劣化约 1.5%,Q4_K 约 0.8%),为第 7 章埋下动机。

第 5 章 Q8_0 与简单块家族

本章补全简单块量化的谱系,并说明为什么在 K-quants 和 IQ 之外,这些"老格式"仍有不可替代的用途。

5.1 Q8_0:近无损基线

5.1.1 34 字节/32 值布局与 \(y_i = d \cdot q_i\)

本节给出 Q8_0 的字节图和公式,说明 i8 有符号码避免了减偏移,有效 bpw 为 8.5。

5.1.2 作为参考实现的 Q8_0

本节论证 Q8_0 的 PPL 劣化通常可忽略(约 +0.1%),因此常被用作"量化正确性验证"的参考:如果一个新格式在 Q8_0 参照下出现异常误差,问题在格式实现而非量化本身。

5.2 非对称变体:Q4_1/Q5_1 与显式零点

5.2.1 Q4_1 的双参数布局

本节介绍 Q4_1 在尺度之外额外存储最小值,公式变为 \(y = d \cdot q + m\),字节开销从 18 增至 20,有效 bpw 约 5.0。

5.2.2 为什么 Q4_1 没有成为主流

本节分析显式零点在 4-bit 下的边际收益不足以抵消额外字节,且 Q4_K 用子块最小值实现了更细粒度的非对称性。

5.3 简单块量化的剩余价值

5.3.1 极小模型与调试场景
5.3.2 兼容性与确定性

这两节说明 Q4_0/Q8_0 的实现最简单、跨后端覆盖最全、数值行为最可预测,因此在嵌入式极端约束、单元测试基准和旧文件兼容中仍有位置。

第三部分 K-quants 核心

第 6 章 K-quants 总览:超级块与两级尺度

本章建立 K-quants 的统一骨架,使后续 Q4_K/Q5_K/Q6_K/Q2_K/Q3_K 的差异表现为"同一公式的不同参数化"。

6.1 超级块与子块划分

6.1.1 256 值超级块与 8 个 32 值子块

本节说明 K-quants 的基本层次:全局尺度 \(d\) 和全局最小值尺度 \(d_{\min}\) 覆盖 256 个值,内部 8 个子块各自拥有 6-bit 尺度和 6-bit 最小值。

6.1.2 为什么是 256 和 32

本节讨论这两个尺寸的工程折中:32 值子块足够小以跟踪局部分布,256 值超级块足够大以摊薄全局参数的存储开销,同时与 SIMD 向量宽度和缓存行对齐兼容。

6.2 通用反量化骨架

6.2.1 \(y_i = d \cdot s_j \cdot q_i - d_{\min} \cdot m_j\)

本节给出 K-quants 的统一公式,明确 \(j\) 是子块索引、\(s_j\) 和 \(m_j\) 是 6-bit 整数、\(q_i\) 是低位量化码。强调这个公式是 Q4_K、Q5_K、Q6_K 的共同祖先。

6.2.2 6-bit 参数的归一化约定:除以 63 还是 64

本节澄清一个实现层面的歧义:6-bit 尺度取值 0--63,反量化时除以 63 还是 64 取决于格式约定和量化端的一致选择,不同文档和 fork 可能不同,跨实现读取时必须对照参考代码。

6.3 层次化尺度为什么有效

6.3.1 误差预算从"一个尺度"变为"尺度 + 子块修正"

本节从信息论角度解释:多出的 12 字节尺度/最小值参数把误差预算分配到 8 个子块,使块内异质性不再由单一尺度承担。

6.3.2 K-quants 家族地图

本节用一张表列出 Q2_K、Q3_K、Q4_K、Q5_K、Q6_K 的子块大小、主体位宽、尺度参数位置和有效 bpw,作为后续各章的导航。

第 7 章 Q4_K:最常用的 K-quant

本章是全书的"主角格式":它既是端侧部署的事实默认,也是理解 6-bit 参数交错打包的最佳载体。

7.1 字节布局:144 字节/256 值

7.1.1 d、dmin、scales[12]、qs[128]

本节给出逐字节图和 block_q4_K 结构体,说明 2+2+12+128=144 的构成,以及 4.5 bpw 的计算过程。

7.1.2 6-bit 尺度与 6-bit 最小值的交错打包

本节详细拆解 12 字节:前 8 字节的低 6 bit 存 8 个尺度、高 2 bit 存最小值低位,后 4 字节存最小值高位。强调这不是"scales 和 mins 各占 12 字节",跨实现读取必须严格遵循 ggml-common.h 的位规则。

7.2 反量化代码路径

7.2.1 6-bit 解包函数

本节走读 get_scale_min_k4 类函数的位操作,给出最小 Python 参考解码器,并用一组手工字节验证输出。

7.2.2 子块尺度与偏移的合成

本节说明 \(d_j = d \cdot s_j / 64\) 和 \(m_j = d_{\min} \cdot m_j / 64\) 的归一化,以及为什么量化端和反量化端必须对"除以 63/64"达成一致。

7.2.3 4-bit 码的解包与乘加循环

本节给出 32 值子块内低/高半字节的提取方式,并与 Q4_0 的循环对比,指出 Q4_K 的主要额外开销在尺度解包而非乘加本身。

7.3 量化端:参数估计与码选择

7.3.1 子块尺度与最小值的联合估计

本节说明量化器如何在每个 32 值子块上选择 \((s_j, m_j)\) 的 6-bit 码,典型做法包括基于子块最大值/最小值的启发式或加权最小二乘搜索。

7.3.2 4-bit 码的计算与夹取

本节给出 \(q_i = \mathrm{clamp}(\mathrm{round}((x_i + m_j)/d_j), 0, 15)\) 的流程,并讨论 \(m_j\) 估计偏差如何导致整体偏移。

7.4 实验:Q4_0 vs Q4_K

7.4.1 相同 bpw 下的逐块误差分布

本节用真实权重切片展示 Q4_K 的误差在块内更均匀,而 Q4_0 的误差集中在低幅度区域。

7.4.2 PPL、KL 与端侧 tok/s

本节给出 Q4_K_M 在多个模型上的 PPL 劣化(约 +0.8%)和端侧实测吞吐,并说明 Q4_K 的解包开销在矩阵乘面前通常不构成瓶颈。

第 8 章 Q5_K:4-bit 主体加高位平面

本章展示 K-quants 如何通过增加一个独立的高位平面把有效位宽从 4.5 推到 5.5,而不改变子块尺度骨架。

8.1 5-bit 的组成

8.1.1 4-bit qs + 1-bit qh 高位平面

本节说明每个值的第 5 bit 集中存储在 qh 中,qs 仍为 4-bit 主体,总字节开销增加但子块结构不变。

8.1.2 block_q5_K 结构体与 bpw

本节给出结构体字段和有效 bpw 约 5.5 的计算,对比 Q4_K 的 4.5。

8.2 反量化:先合并码,再套公式

8.2.1 高位平面的位提取与 5-bit 码合成

本节走读从 qh 提取第 5 bit 并与 qs 低 4 bit 合并的循环,强调位序和子块索引的对应关系。

8.2.2 合并后的 5-bit 码代入 K-quants 公式

本节说明一旦得到 5-bit 码 \(q_i \in 0,31\),后续的 \(d \cdot s_j \cdot q_i - d_{\min} \cdot m_j\) 与 Q4_K 完全一致。

8.3 质量/体积权衡与代码陷阱

8.3.1 Q5_K_M 的 PPL 与速度

本节给出 Q5_K_M 相对 F16 约 +0.4% 的 PPL 劣化和端侧吞吐,说明它是质量敏感场景的中间选择。

8.3.2 高位平面解包中的常见位序错误

本节指出 qh 的字节序和位序是新增格式实现中最常见的 bug 来源,建议用逐值参考实现做交叉验证。

第 9 章 Q6_K:6-bit 子块与输出层

本章说明 Q6_K 的结构偏离了 Q4_K 的"8×32 子块"模式,以及为什么它成为输出嵌入层的默认选择。

9.1 结构:16 个 16 值子块

9.1.1 ql[128] 低 4 bit + qh[64] 高 2 bit

本节给出 Q6_K 的字节布局,说明 6-bit 码如何拆成低 4 位和高 2 位两个平面,以及 16 个 16 值子块的划分。

9.1.2 8-bit 有符号子块尺度 scales[16]

本节说明 Q6_K 的子块尺度是 8-bit 有符号整数,而不是 Q4_K 的 6-bit 无符号,因此不需要 d_min 偏移项。

9.2 公式与反量化

9.2.1 \(y_i = d \cdot \mathrm{scales}j \cdot (q_i - 32)\)

本节推导该公式:6-bit 无符号码减 32 得到对称有符号量,再乘以全局尺度和子块尺度。与 Q4_K 相比,它没有最小值偏移,但子块尺度精度更高。

9.2.2 为什么输出嵌入层常用 Q6_K

本节从敏感度角度解释:输出层直接决定 logits 分布,对量化误差的容忍度低于 FFN 主体,Q6_K 的 6.5 bpw 在"质量---体积"上提供了比 Q5_K 更好的边际收益。

9.3 实验与质量

9.3.1 Q6_K 的 PPL 与 KL
9.3.2 在混合精度配置中的角色

这两节给出 Q6_K 相对 F16 约 +0.2% 的 PPL 劣化,并说明它在 Q4_K_M 文件中通常只占少数张量,但对整体输出质量有超比例影响。

第 10 章 Q2_K 与 Q3_K:极端压缩的边界

本章处理 K-quants 的低位端,说明"再压一点"在哪里失效,以及为什么 Q2_K 不应作为默认。

10.1 Q2_K:16 个 16 值子组与 2-bit 主体

10.1.1 子组划分与尺度/最小值的半字节存储

本节说明 Q2_K 将超级块切成 16 个 16 值子组,每个子组用一个字节的低半字节存尺度系数、高半字节存最小值系数,结构比 Q4_K 更紧凑但精度更低。

10.1.2 \(y = d \cdot d_l \cdot q - d_{\min} \cdot d_h\)

本节给出 Q2_K 的非对称公式,指出它与 Q4_K 数学同构但位打包完全不同,不能复用 Q4_K 的解包代码。

10.2 Q3_K 的 3-bit 组织

10.2.1 3-bit 码的平面拆分与打包

本节简要说明 Q3_K 如何把 3-bit 码拆分为低位和高位平面,以及子块尺度的组织方式。

10.2.2 Q3_K 的 bpw 与质量位置

本节给出 Q3_K 的有效 bpw 约 3.4 和 PPL 劣化量级,说明它在"比 Q2_K 可用、比 Q4_K 小"的狭窄区间内存在。

10.3 极端压缩的边界与适用性

10.3.1 Q2_K 的 PPL 劣化与任务退化
10.3.2 为什么常规部署不推荐 Q2_K 作为默认

这两节用实测数据说明 Q2_K 的劣化不是"线性多一点",而是伴随任务能力的明显退化,因此只应在显存/存储极端受限且可接受质量损失时使用。

第四部分 IQ 系列与码本量化

第 11 章 IQ 系列:重要性矩阵与码本量化

本章是全书范式转换的关键:从"尺度 + 偏移的算术反量化"转向"imatrix 加权的码本查表"。理解 IQ 的关键不是记住 grid 大小,而是理解误差预算如何被重要性重新分配。

11.1 范式切换:为什么要码本

11.1.1 仿射量化在 2 bpw 以下的失效模式

本节说明当码位宽降到 2--3 bit 时,单一尺度下的均匀码字无法覆盖重尾分布,误差急剧上升,K-quants 的层次化尺度也触及瓶颈。

11.1.2 码本 + 重要性:用非线性映射和加权搜索突破瓶颈

本节概述 IQ 的两个核心工具:非线性码本(grid)提供比均匀量化更匹配权重分布的重建点,imatrix 让量化误差按激活重要性加权。

11.2 imatrix:重要性矩阵的生成与使用

11.2.1 llama-imatrix 的校准流程与命令

本节给出 ./llama-imatrix -m model-f16.gguf -f calibration-data.txt -ngl 99 的典型用法,说明校准数据(如 wiki 文本)的选择、chunks 数和上下文长度对 imatrix 质量的影响。

11.2.2 加权目标与 vec_dot 中的 imatrix 索引

本节说明 imatrix 如何以 imatrix + row * n_per_row 的形式传入量化点积,使误差加权在矩阵乘层面生效。

11.3 IQ1_S:1.5 bpw 的查表反量化

11.3.1 2048 项 grid:每项编码 8 个值

本节说明 IQ1_S 使用 2048 项查找表 iq1s_grid,每个表项是一个 8 值有符号向量,反量化时通过 11-bit 索引(qs 8 bit + qh 3 bit)直接查表。

11.3.2 尺度与 delta:\(y = d \cdot (2k+1) \cdot (\mathrm{grid}j \pm \Delta)\)

本节解释 IQ1_S 的尺度不是简单的 \(d \cdot q\),而是带 IQ1S_DELTA 修正的形式,反映码本重建点相对于连续值的偏移。

11.3.3 block_iq1_s 的字节布局与 dequantize_row_iq1_s

本节给出 d、qs[32]、qh 的布局和查表循环,并与 K-quants 的算术循环对比:IQ1_S 的主要开销是索引提取和查表,而非乘加。

11.4 IQ2/IQ3:不同码本容量的谱系

11.4.1 IQ2_XXS/XS/S:256/512/1024 项有符号向量 grid

本节说明 IQ2 系列用不同大小的 grid 覆盖 8 值片段,XXS 最小、S 最大,bpw 从 2.0625 递增,质量随之改善。

11.4.2 IQ3_XXS:256 项 grid,每项 4 个 int8 幅度

本节说明 IQ3_XXS 的 grid 项编码 4 个权重幅度,配合符号位和子块尺度,bpw 约 3.4375。

11.4.3 IQ4_NL:4-bit 非线性格点,块 32

本节介绍 IQ4_NL 这一特殊成员:它沿用 Q4_0 的 32 值块和 18 字节布局,但 4-bit 索引映射到非线性格点 KVALUES_IQ4NL 而非线性码字,因此在相同 bpw 下质量高于 Q4_K。

11.5 质量与工程代价

11.5.1 IQ2_XXS 优于 Q2_K、IQ3_XXS 优于 Q3_K
11.5.2 必须提供 imatrix:量化流程变长
11.5.3 SIMD 查表的实现难度与后端覆盖

这三节用对比数据说明 IQ 的收益,同时指出代价:没有 imatrix 的 IQ 量化质量会显著下降,查表在 SIMD 上的实现比算术反量化更复杂,部分后端(如 Metal/Vulkan)的支持是逐步合入的。

第 12 章 UD_IQ:Unsloth Dynamic 的模型级混合

本章区分"编码格式"和"逐张量分配策略",说明 UD 不是一种新 GGUF 类型,而是一种在已有类型上做敏感度搜索的产品化方法。

12.1 UD 的本质:策略而非编码

12.1.1 按层/按张量动态选择标准 GGML 类型

本节明确 UD 模型内部仍然是 IQ1_S、IQ2_XXS 等已有类型,UD 后缀表示这些类型是如何被分配到头部的。

12.1.2 与 _M 后缀的区别:固定规则 vs 外部搜索

本节对比 llama.cpp 内置的 _M 规则和 Unsloth 的外部敏感度搜索,说明前者可复现但粗粒度,后者更细但依赖工具版本。

12.2 UD-IQ1_S 的实测表现

12.2.1 89% 体积缩减下约 72% top-1 的权衡

本节给出该配置的量化收益与质量保留数据,说明 UD 的价值在"极限压缩下尽量保住任务能力"。

12.2.2 逐张量类型检查:确认 UD 模型内部的实际分布

本节提供一个用 gguf-py 统计 UD 模型张量类型分布的脚本,让读者验证"UD 不是单一格式"。

12.3 UD 的生成流程与生态风险

12.3.1 敏感度矩阵、分配、验证的流程
12.3.2 跨工具可复现性问题

这两节说明 UD 的质量依赖生成时的敏感度数据和分配算法,不同工具/版本可能产出不同的内部类型分布,因此"同名 UD 模型"不一定字节级可复现。

第五部分 三值极限量化

第 13 章 三值量化:STQ1_0 与 PTQ1_0

本章处理位宽的绝对下限:权重被约束到 ({-d,0,+d})。核心问题不是"如何量化",而是"零的位置如何编码",以及运行时需要什么额外变换。

13.1 三值量化的共同约束与不同答案

13.1.1 每个权重取 ({-d,0,+d}),尺度 (d) 共享

本节定义三值格式的基本形式,说明尺度 (d) 通常按块或组共享,质量主要由"零的分布"和"尺度粒度"决定。

13.1.2 零的编码方式决定实际 bpw 和运行时

本节指出纯三值的信息熵约 (\log_2 3 \approx 1.585) bpw,但通过强制稀疏或 base-3 打包,实际存储可以低于或高于该值,而解码复杂度差异巨大。

13.2 STQ1_0:3:4 稀疏三值

13.2.1 每 4 个权重强制 1 个零:32 种模式与 5-bit 编码

本节说明 STQ1_0 的结构规则:每组 4 个权重恰好 1 个零、3 个 (\pm d),共 (4 \times 2^3 = 32) 种模式,用 5 bit(4-bit 码本索引 + 1-bit 符号)编码。

13.2.2 42 字节/256 值 = 1.3125 bpw

本节给出块布局:qs[32] 码本索引 + sign[8] 符号 + 一个 fp16 尺度,总计 42 字节,bpw 为 1.3125。

13.2.3 stride-16 分组与 SIMD 友好性

本节解释 STQ1_0 不是把连续的 (w_0,w_1,w_2,w_3) 分为一组,而是在 64 值块内按 stride-16 分组(如 (w_0,w_{16},w_{32},w_{48})),从而使解码可以直接用 vqtbl2q 和 vdotq_s32 完成,无需重排。

13.2.4 与 TQ1_0/TQ2_0 的位宽对比

本节将 STQ1_0 的 1.3125 bpw 与 TQ2_0 的 2.0625 bpw、TQ1_0 的 1.6875 bpw 对比,说明 STQ1_0 在"更低位宽"和"SIMD 友好"之间选择了前者,代价是稀疏约束带来的质量不确定性。

13.3 PTQ1_0:稠密三值 + base-3 编码

13.3.1 稠密三值:每个权重独立取三值

本节说明 PTQ1_0 不强制稀疏位置,任何权重都可以是零,因此需要 base-3 编码而非模式码本。

13.3.2 28 字节/128 值 = 1.75 bpw

本节给出块布局:24 字节 base-3 数字(每字节 5 trits)+ 2 字节余数(每字节 4 trits)+ 1 个 fp16 尺度,总计 28 字节,bpw 为 1.75。

13.3.3 base-3 解码:(y = d \cdot \mathrm{trit}_i)

本节说明 trit 值通过 base-3 数字解码恢复为 ({-1,0,+1}),再乘以块尺度。

13.3.4 Hadamard 折叠权重:格式之外的必要条件

本节是 PTQ1_0 最关键的工程区别:模型权重以 Hadamard 折叠后的形式存储,推理时必须应用激活侧变换 (y = W' \cdot (H(s \cdot P x))),否则 logits 完全错误。读者必须理解"格式正确但缺失旋转仍然是错的"。

13.4 STQ1_0 vs PTQ1_0:设计取舍

13.4.1 三值约束、实际位宽、编码方式与运行时依赖的对照
13.4.2 质量边界的非线性:为什么不能用 PPL 单指标外推

这两节用对照表总结两个格式的工程画像,并强调三值格式的可用性高度依赖具体模型和任务,PTQ1_0 在 Bonsai-27B 上的 PPL 比接近 1.000,但这不能推广到任意模型。

13.5 type id、后端覆盖与部署风险

13.5.1 STQ1_0 与 PTQ1_0 的 id 归属和 fork 依赖
13.5.2 GPU 后端不完整时的回退路径

这两节讨论三值格式的生态现实:STQ1_0 有 ARM NEON/AVX2 内核但 Metal 覆盖不完整,PTQ1_0 的 Hadamard 变换依赖额外 PR;端侧部署必须验证目标后端是否真正支持该格式,而不是只看 CPU 测试通过。

第六部分 混合精度与工具链

第 14 章 _S/_M/_L 与逐张量混合精度

本章把"格式"上升到"策略":一个可用模型不是所有张量同一种格式,而是按敏感度分配。

14.1 张量敏感度分层

14.1.1 输出嵌入层:对 logits 的直接敏感

本节说明输出层为什么通常使用 Q6_K 而非 Q4_K,以及它对 PPL/KL 的超比例影响。

14.1.2 注意力 QKV、FFN gate/up/down 的差异

本节讨论不同线性层的敏感度差异,以及为什么 FFN down 有时需要更高位宽。

14.1.3 归一化与嵌入:保留 F32 的理由

本节说明 LayerNorm 和部分嵌入对量化噪声的敏感性,以及 F32 张量在文件中的占比虽小但不可省略。

14.2 典型配置的逐张量分布

14.2.1 Q4_K_M:Q4_K 主体 + Q6_K 输出 + F32 归一化

本节用真实文件的类型统计展示 _M 不是单一格式,而是规则驱动的混合。

14.2.2 Q5_K_M、Q3_K_M 等变体的分配差异

本节对比不同 _M 等级在哪些张量上升级/降级,说明后缀字母描述的是"混合策略的激进程度",不是编码格式本身。

14.3 策略生成:规则、imatrix 与搜索

14.3.1 llama.cpp 内置规则的确定性
14.3.2 UD 与外部敏感度搜索的对比

这两节说明内置规则可复现但不一定最优,UD 类方法更细但依赖外部工具和数据,读者应根据部署约束选择。

第 15 章 量化工具链与工作流

本章把前文的所有格式落到可执行的命令流水线,并指出 IQ/UD/三值格式在工具链上的额外依赖。

15.1 转换与量化基础命令

15.1.1 convert_hf_to_gguf.py:从 safetensors 到 F16 GGUF

本节说明转换阶段不做量化,只做容器转换和张量命名映射,输出的 F16 GGUF 是后续所有量化和 imatrix 的起点。

15.1.2 llama-quantize:格式选择与输出

本节给出 llama-quantize input-f16.gguf output-Q4_K_M.gguf Q4_K_M 的用法,说明 Q4_K_M 是一个策略名而非单一 ggml_type。

15.2 imatrix 与 IQ 量化工作流

15.2.1 校准数据准备与 llama-imatrix 参数

本节重复并深化第 11.2 节,给出 --chunks、-b、-c、-ngl 的典型值,说明校准数据规模和领域匹配对 IQ 质量的影响。

15.2.2 --imatrix 在 llama-quantize 中的传入

本节说明 IQ 量化必须提供 imatrix,缺少时质量会显著下降;并指出这不是可选项而是格式设计的一部分。

15.3 三值格式的工具链差异

15.3.1 PTQ1_0 的 Hadamard 折叠依赖
15.3.2 分支/fork 与上游合并状态

这两节说明 STQ1_0 和 PTQ1_0 的量化端可能不在上游主线中,PTQ1_0 还需要额外的 Hadamard 变换 PR;读者必须确认工具链版本匹配,而不是假设"格式名可识别即可量化"。

15.4 验证与部署前检查

15.4.1 类型分布检查
15.4.2 PPL/KL 快速验证与后端冒烟测试

这两节给出部署前的最小检查清单:用 gguf-py 统计类型分布、用 llama-perplexity 对比 base 模型、在目标后端上跑一次短生成,确保没有解包错误或缺失变换。

第七部分 实现与性能

第 16 章 llama.cpp 量化代码架构

本章从"想新增一个格式该改哪些文件"的角度反向解剖 llama.cpp 的量化架构,使读者不仅会读代码,还能安全地扩展。

16.1 类型注册的三个文件

16.1.1 ggml.h:type id 与 GGML_TYPE_COUNT

本节说明新增格式首先要在 ggml.h 中分配 type id、更新 GGML_TYPE_COUNT 和 ggml_ftype 枚举。

16.1.2 ggml-common.h:块结构体与 static_assert

本节说明块结构体必须在这里定义,并用 static_assert 锁定大小,确保与磁盘布局一致。

16.1.3 ggml.c:type_traits 分派表

本节说明 type_traits 条目如何把 type id 映射到块大小、类型大小和 to_float/from_float 函数指针,缺少条目会导致运行时无法分派。

16.2 量化与反量化函数契约

16.2.1 quantize_row_* 与 dequantize_row_*

本节说明这两个函数的签名约定:输入/输出指针、元素计数、块对齐要求,以及参考实现和 SIMD 优化实现的关系。

16.2.2 vec_dot_*:避免完整反量化的点积

本节解释量化矩阵乘的核心优化是在 vec_dot 中直接消费量化块,而不是先反量化再点积;IQ 的查表路径和 K-quants 的算术路径在这里分叉。

16.3 新增格式的最小步骤

16.3.1 结构体 → 参考量化/反量化 → type_traits → 测试
16.3.2 后端扩展:CPU SIMD → Metal → CUDA/Vulkan

这两节给出一个可操作的清单:先在 CPU 上实现参考版本并通过 test-quantize-fns,再逐步扩展 SIMD 和 GPU 后端;每一步都需要与 gguf-py 的字节输出做交叉验证。

第 17 章 反量化与矩阵乘性能

本章回答一个实践问题:不同格式的"解码开销"在端侧到底占多少,以及为什么 Q4_K 的额外尺度解包通常不拖慢推理。

17.1 内存带宽瓶颈下的格式选择

17.1.1 解码阶段的算术强度

本节用 roofline 说明自回归解码是带宽受限的,因此 bpw 越低,理论 tok/s 越高,但低 bpw 格式的查表/解包开销可能抵消收益。

17.1.2 Q4_0 vs Q4_K vs IQ1_S 的真实速度对比

本节给出端侧实测的 tok/s 量级,并解释 Q4_K 的尺度解包在矩阵乘面前通常隐藏,而 IQ1_S 的查表路径在某些后端上可能成为瓶颈。

17.2 SIMD 与后端覆盖

17.2.1 K-quants 的向量化加载与寄存器布局

本节说明 K-quants 的 6-bit 解包如何用 SIMD 位操作实现,以及为什么块大小与向量宽度对齐很重要。

17.2.2 三值格式的查表与 vqtbl2q/vdotq_s32

本节以 STQ1_0 为例说明三值格式如何利用 ARM NEON 的查表指令实现无重排解码,以及这种设计对 GPU 后端可移植性的限制。

17.3 端侧基准方法

17.3.1 tok/s、TTFT、冷启动与 P95 延迟
17.3.2 不同芯片组的后端选择与延迟方差

这两节强调端侧基准不能只看平均 tok/s,必须测量首 token 延迟和 P95 延迟;不同芯片组(Snapdragon、Tensor、Dimensity、Exynos)的 GPU delegate 延迟方差从 ±12% 到 ±52%,后端选择策略本身就是部署质量的一部分。

第 18 章 正确性验证与质量评估

本章提供一套从"字节级"到"任务级"的验证框架,使读者能够独立判断一个格式实现是否正确、一个量化模型是否可用。

18.1 逐块与逐张量验证

18.1.1 参考实现交叉验证:C vs gguf-py

本节说明任何新格式的参考反量化必须与独立实现(如 Python)逐字节比对,这是发现位序/打包错误最有效的手段。

18.1.2 最大绝对误差与相对 L2

本节给出 test-quantize-fns 类测试的阈值设定方法,说明"参考实现误差为零"是格式定义正确的必要条件。

18.2 全模型质量评估

18.2.1 PPL、KL 与 base 模型对比

本节说明 llama-perplexity 的 --kl-divergence-base 用法,以及为什么 IQ/三值格式更依赖 KL 而非单一 PPL。

18.2.2 任务评测与长上下文测试

本节指出 PPL 不捕捉的退化(如指令跟随、长上下文一致性)必须用任务评测补充,三值格式尤其需要逐模型验证阈值效应。

18.3 特殊格式的验证要点

18.3.1 PTQ1_0 的 Hadamard 变换正确性
18.3.2 STQ1_0 的 3:4 模式完整性

这两节给出格式特定的检查:PTQ1_0 必须验证激活侧变换已应用,STQ1_0 必须验证每组 4 lane 恰好一个零,否则解码结果系统性错误。

第八部分 生态与演进

第 19 章 跨引擎与格式互操作

本章处理一个容易被忽视的问题:GGUF 不是 llama.cpp 的私有格式,但不同读取器对"同一格式"的理解可能因字节打包约定不同而分叉。

19.1 自研读取器的边界

19.1.1 用 Python/Rust/JS 解析 GGUF 的最小集

本节说明除 header 和 tensor_infos 外,读取器必须实现每个格式的精确解包;仅支持 Q4_0/Q4_K 的读取器无法声称"支持 GGUF"。

19.1.2 gguf-py 作为参考语义的权威性

本节建议以 gguf-py 的 dequantize 输出作为跨实现比对的 ground truth,任何自研读取器的偏差都应先怀疑自己。

19.2 跨格式互操作的陷阱

19.2.1 6-bit 打包、base-3 编码、Hadamard 元数据的约定差异

本节列举最容易分叉的三类约定:K-quants 的 6-bit 交错、PTQ1_0 的 base-3 数字顺序、Hadamard 折叠所需的元数据字段。这些不是"实现风格",而是格式定义的一部分。

19.2.2 type id 冲突与版本兼容矩阵

本节给出一个互操作测试矩阵:格式 × 引擎 × 后端 × 版本,帮助读者判断某个组合是否可用。

19.3 与训练生态的转换路径

19.3.1 safetensors → GGUF 的转换保真度
19.3.2 量化不可逆:不存在无损往返

这两节重申 GGUF 量化的有损本质,以及转换/量化链路中唯一需要保证的是"量化前 F16 GGUF 与原始权重的数值一致"。

第 20 章 未来与标准化

本章收束全书,讨论 GGUF 量化面临的生态治理问题和技术演进方向,并回到"格式设计为什么是这样"的主线。

20.1 更低比特的硬件友好布局

20.1.1 三值与 2-bit 的混合方向

本节讨论 STQ1_0/PTQ1_0 之后可能出现的设计:结构化稀疏与码本的结合、按硬件查表能力定制的码本大小。

20.1.2 量化与 KV cache 量化的交互

本节指出权重格式不是孤立问题,KV cache 量化(如 Q3_K/V 或专用格式)会改变端侧内存预算的分配,权重格式的选择需要与 KV 策略联合考虑。

20.2 生态治理:type id、分支与标准

20.2.1 自定义 id 与主线合并的冲突

本节以 STQ1_0/PTQ1_0 的 id 归属为例,讨论一个现实矛盾:格式创新往往先发生在 fork/PR 中,但 type id 的全局唯一性要求主线治理,否则会出现"同一 id 不同格式"或"同一格式不同 id"。

20.2.2 格式文档化与跨实现测试的最小要求

本节提出一个格式被生态接受的必要条件:字节布局的精确文档、参考反量化实现、跨实现测试向量,而不仅仅是"在某个 fork 上能跑"。

20.3 全书总结:三条公式主线

20.3.1 从 (y = d(q-8)) 到 (y = d s_j q - d_{\min} m_j)
20.3.2 从算术反量化到码本查表
20.3.3 从稠密权重到结构化三值

这三节用三条公式概括全书:第一范式用单一尺度,第二范式用层次化尺度,第三范式用码本或三值约束;每一次跃迁都是在"位宽---质量---解码复杂度---生态覆盖"之间重新分配预算。读者应能据此判断一个新格式属于哪一范式、解决了什么、代价是什么。

附录

  • 附录 A 公式速查:按格式列出量化/反量化公式、尺度归一化约定和有效 bpw 计算。
  • 附录 B 字节布局图集:Q4_0、Q4_K、Q5_K、Q6_K、Q2_K、IQ1_S、IQ2_XXS、IQ3_XXS、IQ4_NL、STQ1_0、PTQ1_0 的逐字节图与结构体对照。
  • 附录 C 最小 Python 参考实现:GGUF 解析器,Q4_0/Q4_K/Q6_K/IQ1_S/STQ1_0/PTQ1_0 解码器,以及与 gguf-py 的交叉验证脚本。
  • 附录 D gguf-py API 速查 :GGUFReader、GGUFWriter、dequantize、GGMLQuantizationType 的常用调用模式。
  • 附录 E llama.cpp 关键函数索引 :按格式列出 quantize_row_*、dequantize_row_*、vec_dot_* 的源文件位置。
  • 附录 F 实验脚本与命令:转换、imatrix、量化、PPL/KL、端侧基准的完整命令。
  • 附录 G 术语表:block、super-block、imatrix、grid、trit、Hadamard 折叠、stride-16 分组等。
  • 附录 H 参考文献与版本说明:Sherry(STQ1_0)、QuIP# 系(IQ)、Prism Hadamard、llama.cpp PR 编号与锁定版本。