量化是 llama.cpp 能把一个 7B、甚至 70B 大模型塞进消费级显卡 / 内存的"压缩术"。课 01 提过它有 4/5/8 bit 多种档位; 这一课讲清三件事:为什么能压、怎么压(块量化)、Q4_0 / Q8_0 / K-quant 各是什么。 硬核的字节级细节留到第三部分的"量化格式"课,这里先把直觉建立起来。
训练好的大模型,本质是一大堆浮点数(权重)。默认用 16 位浮点(FP16 / BF16)存,每个权重 2 字节——一个 70 亿(7B)参数的模型, 光权重就要 约 14 GB,普通显卡和内存直接被劝退。量化就是用更少的位数近似地存这些权重:8 bit 砍掉一半、4 bit 再砍一半, 显存需求成倍下降。
每权重 2 字节
7B -> 约 14 GB
约 1 字节 / 权重
7B -> 约 7 GB
约 0.56 字节 / 权重
7B -> 约 3.9 GB
为什么大模型能被压到这么狠还能用?因为它的权重里有大量冗余:每个权重的具体数值并不需要那么高的精度,模型真正依赖的是这些权重 整体的统计规律。把每个数从"非常精确"降到"大致正确",单看一个损失明显,但成千上万个权重一起作用时,误差很大程度上互相抵消,最终输出几乎不受影响。 这也是为什么 4-bit 量化常常只让模型质量掉一点点,却换来几倍的体积和速度收益。
那为什么大家最常用 4-bit 而不是更狠的 2-bit、或更稳的 8-bit?这是个"甜点"问题:8-bit 几乎不掉质量,但省得不够多; 2-bit、3-bit 省得很狠,质量却开始明显滑坡。4-bit(尤其是 K-quant 的 4-bit)落在曲线的拐点上——体积压到原来的约四分之一,质量却只掉一点点, 于是成了社区下载量最大的档位。具体选哪档,还要看你的硬件、对质量的容忍度,以及模型本身大小(越大的模型,往往越扛得住激进量化)。
顺便补一点背景:前面反复出现的 FP32 / FP16 / BF16 都是浮点格式,区别在用多少位、以及怎么分配给"指数"和"尾数"。 FP32 是 4 字节的全精度,训练时常用;FP16 和 BF16 都是 2 字节的半精度,其中 BF16 牺牲一点尾数精度,换来和 FP32 一样大的表示范围,在大模型里很受欢迎。 量化则更进一步,把这些浮点直接换成更省的整数表示——可以理解为"在 FP16 已经省了一半的基础上,再往下狠压一截"。
那"用更少位数近似"具体怎么做?最朴素的想法:给整个权重矩阵定一个统一的缩放系数(scale),把浮点数等比例映射到一个小整数范围。 但问题是,同一个矩阵里权重的大小可能差很多,用一个全局 scale,大值和小值没法兼顾,误差会很大。
块量化的办法是:把权重切成一个个小块(Q4_0 里每块 32 个权重),每块各自配一个 scale。块内动态范围小、scale 贴得准, 近似自然更精确。这就是"分而治之"在量化上的体现:
再换个角度理解"为什么块内范围小就更准":量化的本质是"用有限的几档去近似连续的值",这几档要覆盖的范围越窄,每一档之间的间隔就越小、分得越细。 一整个矩阵里既有 0.001 也有 5.0,硬用 16 档去分,间隔必然很粗;可一旦切成小块,每块内部的数往往挤在相近的量级,同样 16 档分得就细多了。这就是块量化精度更高的根本道理。
落到代码上,Q4_0 的"一块"就是一个紧凑的结构体(来自 ggml/src/ggml-common.h):
#define QK4_0 32 // 一块 32 个权重 typedef struct { ggml_half d; // scale (fp16), 每块一个 uint8_t qs[QK4_0/2]; // 32 个权重, 每个压成 4-bit, 两个挤一字节 } block_q4_0; // 2 + 16 = 18 字节 -> 平均 4.5 bit/权重
算笔账:一块 32 个权重,用 2 字节存 scale(半精度浮点 ggml_half)、16 字节存 32 个 4-bit 量化值 (每个权重 4 bit,两个挤进一个字节),合计 18 字节。摊到每个权重就是 18 × 8 / 32 = 4.5 bit——这就是"4-bit 量化"实际占用稍多于 4 bit 的原因:那多出来的 0.5 bit,是每块都要分摊的那个 scale。
用的时候要"解量化":把存的 4-bit 整数还原成近似的浮点值。Q4_0 的规则很简单(对应 ggml/src/ggml-quants.c 的 dequantize_row_q4_0):
# 每个 4-bit 值 q 在 0..15, 还原成带符号的权重: for i in range(32): q = nibble(qs, i) # 0..15 x[i] = (q - 8) * d # 减 8 居中到 0 附近, 再乘以这一块的 scale d
反过来,怎么从浮点权重得到那些 4-bit 整数?以对称量化为例:先在这一块里找出绝对值最大的那个权重,用它定出 scale——Q4_0 的具体做法是 d = max / -8(max 是块内带符号的极值权重),相当于把这个极值锚定到量化范围的端点 q=0,从而用满 -8..7 整个范围;再把每个权重除以 d、四舍五入、加上偏移,压进 0..15。所以"量化"和"解量化"是一对互逆操作: 量化时 q = round(x/d) + 8,用时 x ≈ (q-8)*d。两次取整之间丢掉的那点零头,正是量化误差的来源。
顺带一问:块大小为什么常取 32?这又是一处权衡——块越小,scale 越贴合局部、精度越高,但要存的 scale 越多、压缩率下降;块越大则相反。 32 是精度和体积之间一个经过实践检验的折中,也正好契合硬件上常见的并行宽度,算起来顺手。K-quant 用 256 的超块再细分,则是想"既要大块的高压缩率、又要小块的高精度", 试图鱼和熊掌兼得。
把一组真实权重压一遍再还原,"有损但损得很小"就看得见了:
Q4_0 只是量化大家庭里的一个。同样的"块 + scale"思路,换一换参数,就是不同档位:
| 类型 | 每权重 bit | 块 / 超块 | 特点 |
|---|---|---|---|
| Q8_0 | 约 8.5 bit | 32 | 最接近 FP16,体积大,质量最稳 |
| Q4_0 | 约 4.5 bit | 32 | 最轻最快,精度损失较明显 |
| Q4_K | 约 4.5 bit | 超块 256 | 子块各有 scale + 混合精度,同 bit 下更准 |
Q8_0 用 8 bit 存每个权重(块还是 32,结构是 d + 32 个 int8),约 8.5 bit/权重,体积大但精度最接近原始 FP16, 常用于对质量最敏感的场合。Q4_0 最轻,4.5 bit,速度快、省显存,但精度损失相对明显。
而 K-quant(名字里带 K,如 Q4_K、Q5_K)是更聪明的一档:它用更大的"超块" (super-block,QK_K = 256 个权重),超块内再分成若干子块,每个子块有自己更精细的 scale 和 min,还会对不同张量混合用不同位宽。 结果是:在同样的平均 bit 数下,K-quant 的困惑度(perplexity,衡量模型预测好坏的指标,越低越好)通常明显低于对应的 Q4_0 / Q5_0。 今天大家从网上下载的 GGUF,大多就是 Q4_K_M 这类 K-quant 档位。
把这些名字连起来读就有规律了:字母 Q + 位数 + 可选的 K + 可选的档位后缀。Q4_0 是"4-bit、对称、基础块", Q4_K_M 是"4-bit、K-quant、中档混合精度",Q8_0 是"8-bit、对称、基础块"。下次在下载页面看到一长串 Q3_K_S、Q5_K_M、Q6_K,你就能一眼读出它大概多大、多准了。
typedef struct { ggml_half d; // 超块整体 scale ggml_half dmin; // 超块整体 min (非对称) uint8_t scales[...]; // 各子块更细的 scale/min (6-bit, 已量化) uint8_t qs[...]; // 量化值 } block_q4_K; // QK_K = 256, 简化自 ggml-common.h
还有两点值得知道。其一,llama.cpp 量化的主要是"权重",推理时流动的"激活值"通常仍用较高精度(如 fp16/fp32)计算——因为权重是静态的、 占绝大多数内存,最值得压;激活是动态的,过度量化更容易伤精度。其二,并非每个张量都用同一档:像词嵌入、输出投影这种对质量影响大的张量, 常被刻意保留在更高的位宽,这正是 K-quant 的 _M / _L 档在做的"混合精度"。
怎么衡量"量化掉了多少质量"?最常用的指标是困惑度(perplexity):拿一段标准文本,看模型对"下一个词"预测得有多准,困惑度越低越好。 社区常做的事,就是把同一个模型的各个量化档位都跑一遍困惑度、列成表对比——你会看到 Q8_0 几乎和 fp16 持平、Q4_K_M 只高一丁点,而 Q2_K 则明显抬高。 llama-perplexity 工具就是干这个的,第五部分的 L30 会专门讲怎么用它给量化"打分"。一个经验法则:在显存放得下的前提下,尽量选高一档,质量更有保障。
最后澄清一个边界:量化只压缩权重的表示方式(每个数用几位存),并不改变模型的结构——层数、维度、参数个数都原封不动。 这和剪枝(删掉一部分权重 / 神经元)、蒸馏(训练一个更小的新模型去模仿大模型)是完全不同的三条压缩路线。量化最大的好处就是 几乎免费、即插即用:不用重新训练,一个命令就能把现成模型变小变快,这也是它在本地推理里如此普及的原因。
还有个实用细节:不同硬件后端对量化格式的支持和优化程度并不一样。同一个 Q4_K 模型,在 CPU 上靠 SIMD 指令快速解量化、在 CUDA 上有专门的核函数, 速度表现可能差别不小。所以"选哪个量化档位"有时也要看你打算在什么硬件上跑——这部分在第六部分讲内核时会更具体。整体而言,主流的 Q4_K / Q8_0 在各后端都有良好支持,闭眼选基本不会错。
下面三个问题,想深究的同学点开看;只想抓主线的可以先跳过。
数字是每个权重的 bit 数:Q4 = 4 bit、Q8 = 8 bit。后缀 _0 / _1 区分量化的"对称性"。
_0 是对称量化,只存一个 scale、零点固定(就像前面 Q4_0 的 (q-8)*d);_1 是 非对称量化,额外再存一个最小值 min(偏移量),还原公式变成 q*d + min。多存一个 min 让它能更贴合那些 "不以 0 为中心"的权重分布,精度略高,但每块要多占几字节。要不要这个 min,就是 _0 和 _1 的区别。
关键在更细粒度的 scale。普通 Q4_0 是"32 个权重共享 1 个 scale";K-quant 用 256 个权重的超块,但超块内部再切成多个子块, 每个子块有自己的 scale 和 min(这些子块 scale 本身又被量化成 6-bit 存起来,省空间)。粒度越细,scale 越能贴合局部,误差越小。
名字里的 _S / _M / _L(small / medium / large)是不同的"混合精度"档: 对模型里更重要的层用稍高的位宽、不重要的用低位宽,在体积和精度之间取不同平衡。所以同样标着"4-bit",Q4_K_M 往往比 Q4_0 又准又只大一点点——这也是它成为主流下载格式的原因。
imatrix(importance matrix,重要性矩阵)常被误解为"决定每个权重用几 bit"——其实不是。它是用一批校准数据 跑一遍模型,统计出"每个权重对最终输出的影响有多大"。
量化时,对更重要的权重,让它的量化误差更小(在选 scale、取整时更偏向保住它们)。换句话说,imatrix 改变的是"误差怎么分配", 让宝贵的精度用在刀刃上,而不改变位宽分配本身。它由 llama-imatrix 工具生成,再喂给 llama-quantize 一起用,通常能在不增大体积的前提下进一步降低困惑度。
Quantization is the "compression trick" that lets llama.cpp fit a 7B - or even 70B - model into consumer GPU / RAM. Lesson 01 mentioned its 4/5/8-bit tiers; this lesson nails down three things: why it compresses, how it compresses (block quantization), and what Q4_0 / Q8_0 / K-quant are. The hardcore byte-level details wait for Part 3's "quantization format" lesson; here we build the intuition first.
A trained model is essentially a huge pile of floating-point numbers (weights). Stored by default in 16-bit float (FP16 / BF16), each weight is 2 bytes - a 7-billion-parameter (7B) model needs about 14 GB for weights alone, which ordinary GPUs and RAM simply refuse. Quantization approximates these weights with fewer bits: 8-bit halves it, 4-bit halves it again, dropping memory needs several-fold.
2 bytes / weight
7B -> ~14 GB
~1 byte / weight
7B -> ~7 GB
~0.56 bytes / weight
7B -> ~3.9 GB
Why can a big model be squeezed this hard and still work? Because its weights carry a lot of redundancy: each weight's exact value need not be so precise; what the model really relies on is the overall statistical pattern of the weights. Dropping each number from "very precise" to "roughly right" looks lossy one at a time, but across thousands of weights the errors largely cancel, leaving the output almost unaffected. That is why 4-bit quantization often costs only a sliver of quality for several-fold gains in size and speed.
So why is 4-bit the go-to rather than more aggressive 2-bit or safer 8-bit? It is a "sweet spot" question: 8-bit barely loses quality but saves too little; 2-bit and 3-bit save a lot but quality starts to slide noticeably. 4-bit (especially K-quant 4-bit) sits at the knee of the curve - about a quarter the size, only a sliver of quality lost - so it is the most-downloaded tier. Which exact tier still depends on your hardware, your quality tolerance, and the model's own size (bigger models usually withstand aggressive quantization better).
A bit of background: the FP32 / FP16 / BF16 that keep coming up are all floating-point formats, differing in how many bits they use and how they split them between "exponent" and "mantissa". FP32 is 4-byte full precision, common in training; FP16 and BF16 are both 2-byte half precision, with BF16 trading some mantissa precision for the same wide range as FP32, which large models like. Quantization goes a step further, replacing these floats with cheaper integer representations - think of it as "squeezing further down, on top of the half FP16 already saved".
So how exactly do we "approximate with fewer bits"? The naive idea: pick one global scale for the whole weight matrix and map the floats proportionally into a small integer range. The problem: weight magnitudes within one matrix can vary a lot, and a single global scale cannot serve both large and small values well, so the error is big.
Block quantization's answer: cut the weights into small blocks (32 weights per block in Q4_0) and give each block its own scale. A block's dynamic range is small, the scale fits tightly, and the approximation is naturally more accurate. This is "divide and conquer" applied to quantization:
Another way to see "why a small in-block range is more accurate": quantization approximates continuous values with a few fixed levels, and the narrower the range those levels must cover, the smaller the gap between levels and the finer the quantization. A whole matrix holding both 0.001 and 5.0 forced into 16 levels has coarse gaps; but cut into small blocks, the numbers in each block usually cluster at a similar magnitude, so the same 16 levels resolve them far more finely. That is the root reason block quantization is more accurate.
In code, one Q4_0 "block" is a compact struct (from ggml/src/ggml-common.h):
#define QK4_0 32 // 32 weights per block typedef struct { ggml_half d; // scale (fp16), one per block uint8_t qs[QK4_0/2]; // 32 weights, each 4-bit, two packed per byte } block_q4_0; // 2 + 16 = 18 bytes -> 4.5 bit/weight on average
Do the math: a block of 32 weights uses 2 bytes for the scale (half-precision ggml_half) and 16 bytes for 32 4-bit quants (4 bits each, two packed into a byte), totaling 18 bytes. Per weight that is 18 x 8 / 32 = 4.5 bit - which is why "4-bit quantization" actually takes a bit more than 4 bits: the extra 0.5 bit is the per-block scale, amortized over the block.
To use it you "dequantize": restore the stored 4-bit integers to approximate floats. Q4_0's rule is simple (cf. dequantize_row_q4_0 in ggml/src/ggml-quants.c):
# each 4-bit value q in 0..15, restored to a signed weight: for i in range(32): q = nibble(qs, i) # 0..15 x[i] = (q - 8) * d # subtract 8 to center near 0, then scale by the block's d
Conversely, how do you get those 4-bit integers from float weights? For symmetric quantization: find the largest-magnitude weight in the block and use it to set the scale; Q4_0's recipe is d = max / -8 (max is the block's signed extreme weight), anchoring that extreme to the end of the range (q=0) so the full -8..7 range is used; then divide each weight by d, round, and add the offset to pack into 0..15. So "quantize" and "dequantize" are inverse operations: quantize with q = round(x/d) + 8, use with x ~= (q-8)*d. The little remainder lost between the two roundings is exactly where quantization error comes from.
A side question: why is the block size often 32? Another trade-off - smaller blocks fit the local scale better and raise accuracy but need more scales, lowering the compression ratio; larger blocks do the reverse. 32 is a practice-tested compromise between accuracy and size, and it also matches common hardware parallel widths, so it computes nicely. K-quant's 256-weight super-block with sub-division then tries to get "both the high compression of big blocks and the high accuracy of small ones".
Push a few real weights through and back, and "lossy but barely" becomes visible:
Q4_0 is just one member of the quantization family. The same "block + scale" idea, with different parameters, gives different tiers:
| Type | bits / weight | block / super-block | character |
|---|---|---|---|
| Q8_0 | ~8.5 bit | 32 | closest to FP16, large, most stable quality |
| Q4_0 | ~4.5 bit | 32 | lightest and fastest, more visible accuracy loss |
| Q4_K | ~4.5 bit | super-block 256 | per-sub-block scales + mixed precision, more accurate at the same bits |
Q8_0 stores each weight in 8 bits (block still 32, struct is d + 32 int8), ~8.5 bit/weight - large but closest to the original FP16, used where quality matters most. Q4_0 is lightest at 4.5 bit, fast and memory-saving, but with more visible accuracy loss.
And K-quant (the ones with K, like Q4_K, Q5_K) is a smarter tier: it uses a larger "super-block" (QK_K = 256 weights), splits it into several sub-blocks each with its own finer scale and min, and even mixes bit-widths across tensors. The result: at the same average bits, K-quant's perplexity (a measure of prediction quality, lower is better) is usually clearly lower than the matching Q4_0 / Q5_0. The GGUF files people download today are mostly K-quant tiers like Q4_K_M.
Read these names together and a pattern emerges: letter Q + bit count + optional K + optional tier suffix. Q4_0 is "4-bit, symmetric, basic block", Q4_K_M is "4-bit, K-quant, medium mixed precision", Q8_0 is "8-bit, symmetric, basic block". Next time you see a long list of Q3_K_S, Q5_K_M, Q6_K on a download page, you can read off roughly how big and how accurate each is.
typedef struct { ggml_half d; // super-block overall scale ggml_half dmin; // super-block overall min (asymmetric) uint8_t scales[...]; // finer per-sub-block scale/min (6-bit, quantized) uint8_t qs[...]; // quants } block_q4_K; // QK_K = 256, simplified from ggml-common.h
Two more things worth knowing. First, llama.cpp mainly quantizes "weights"; the "activations" flowing during inference are usually still computed in higher precision (fp16/fp32) - weights are static and dominate memory, so they are most worth compressing, while activations are dynamic and over-quantizing them hurts accuracy more easily. Second, not every tensor uses the same tier: high-impact tensors like the embedding and output projection are often deliberately kept at higher bit-widths - exactly the "mixed precision" the K-quant _M / _L tiers do.
How do you measure "how much quality quantization cost"? The most common metric is perplexity: take a standard text and see how well the model predicts the "next word" - lower is better. A common community exercise is to run perplexity across a model's quant tiers and tabulate them - you will see Q8_0 nearly matching fp16, Q4_K_M only a hair higher, and Q2_K clearly higher. The llama-perplexity tool does exactly this, and Part 5 (L30) covers using it to "grade" quantization. A rule of thumb: pick one tier higher whenever memory allows, for safer quality.
Finally, a boundary to clarify: quantization only compresses the weights' representation (how many bits each number uses); it does not change the model's structure - layer count, dimensions, parameter count all stay. That makes it a different path from pruning (removing some weights/neurons) and distillation (training a smaller new model to mimic a big one). Quantization's big advantage is being nearly free and plug-and-play: no retraining, one command shrinks and speeds up an existing model - which is why it is so ubiquitous in local inference.
Another practical detail: different hardware backends support and optimize quant formats to different degrees. The same Q4_K model may perform quite differently dequantizing via SIMD on CPU versus a dedicated kernel on CUDA. So "which tier" sometimes also depends on what hardware you will run on - Part 6 on kernels gets concrete about this. Overall, the mainstream Q4_K / Q8_0 are well supported on all backends, so you can pick them blind without much risk.
Three questions below; open them if you want depth, skip them if you only want the main line.
The number is bits per weight: Q4 = 4-bit, Q8 = 8-bit. The suffix _0 / _1 distinguishes the quantization's "symmetry".
_0 is symmetric - it stores only a scale with a fixed zero point (like Q4_0's (q-8)*d above); _1 is asymmetric - it stores an extra minimum (offset), so the formula becomes q*d + min. The extra min lets it fit weight distributions that are "not centered on 0", giving slightly better accuracy at a few extra bytes per block. Whether to keep that min is exactly the _0 vs _1 difference.
The key is finer-grained scales. Plain Q4_0 is "32 weights share 1 scale"; K-quant uses a 256-weight super-block but cuts it into several sub-blocks, each with its own scale and min (these sub-block scales are themselves quantized to 6 bits to save space). Finer granularity means the scale fits the local data better, so error shrinks.
The _S / _M / _L (small / medium / large) in the name are different "mixed-precision" tiers: use slightly higher bits for the model's more important layers and lower bits for the rest, balancing size and accuracy differently. So even labeled "4-bit", Q4_K_M is often both more accurate and only slightly larger than Q4_0 - which is why it became the mainstream download format.
imatrix (importance matrix) is often misread as "deciding how many bits each weight gets" - it is not. It runs the model over a batch of calibration data to measure "how much each weight influences the final output".
During quantization, more important weights are kept with smaller quantization error (the scale choice and rounding lean toward preserving them). In other words, imatrix changes how the error is distributed, spending precious precision where it matters, while not changing the bit-width allocation itself. It is produced by the llama-imatrix tool and fed to llama-quantize, usually lowering perplexity further without increasing size.