均值不偏别乱用 Q4_1!用误差数学算一算 llama.cpp 对称与非对称量化的真实损耗

📅 2026/6/27 0:49:19
均值不偏别乱用 Q4_1!用误差数学算一算 llama.cpp 对称与非对称量化的真实损耗
llama.cpp 把 Q4_0 量化的取整写成(int8_t)(x*id + 8.5f),把 Q4_1 写成(int8_t)(x*id + 0.5f)——两种格式只差一个加法常数里的那个 8。你大概会这么想:Q4_1 不过是 Q4_0 加了个min字段,每块多 2 个字节,把点积内核照抄过来、改一改反量化就行。错。它俩的点积内核连一行都对不上:Q4_0 在循环里给每个 nibble 减 8,Q4_1 一个 8 都不减,反而多算一个m·s的交叉项。更要命的是那个s。它不在权重块里,而在激活块里——Q4_1 逼着配套的激活量化从 Q8_0(34 字节/块)换成 Q8_1(36 字节/块,多存一个s = d·Σq)。一个权重格式选了非对称,代价传染到了激活格式。所以"Q4_1 比 Q4_0 多 0.5 bpw"这句话是骗你的。它多的不止权重那 0.5 个 bit,是一条从块布局、取整常数、反量化公式、最优 scale 优化器的维度,一路烧到激活格式和点积交叉项的链子。这篇文章从两个结构体的 18 vs 20 字节出发,把这条传染链一节一节焊死。读完你能手写出和 GGML逐字节一致的两份实现,并带走一张判据明确的选型表:什么时候这 0.5 bpw 值得花,什么时候是纯浪费。一、一个加法常数,撕开两种格式先把全景摆出来,后面九节都是在给这张全景填证据。Q4_0 和 Q