真正的工程不是追求"最好",而是知道自己要什么。
量化大模型,所有人都默认精度要打折。但今天这个项目里,有一种压缩,连一个比特都不差——而且,同一个模型上,还同时躺着另外两种完全不同的玩法。
一个模型,三种死法?不,三种活法
先交代背景。
我手头有一个 1B 的小模型,MiniCPM5-1B,Llama 架构,权重 2.16GB。在 4090 上跑得好好的——36 毫秒一次前向,2.2GB 显存。
但我做量化的目的,从来不是为了"在 4090 上跑得快"。我的目标是:让它在更小的地方活着。
手机、平板、边缘盒子——这些地方的内存,是以"GB"为单位心疼的。2.16GB 的权重,对它们来说是奢侈品。
于是,同一份权重,我试了三种思路。
第一种,叫完美主义:一个比特都不能错。
第二种,叫实用主义:可以损失一点,但必须压得狠。
第三种,叫探索主义:我也不知道行不行,先试试。
这三种思路,最后变成了同一个仓库里的三套方案:BF16X、DG 4-bit、TDM-PTQ。
它们之间没有谁赢谁输——因为它们的目标根本不一样。
先看第一剑。它是我上一次视频的主角,但今天我会用一个更扎心的角度讲它。
第一剑·完美主义:BF16X,一个比特都不差
BF16X,无损压缩,压缩比 2.08 倍:2161MB 压到 1041MB,解压出来和原始权重逐比特完全一致。
它干的事,一句话就能说清:bf16 的权重,相邻元素的指数往往差不多。那就别重复存了——每 16 个权重共享一个最大指数,每个权重只存 3 位差值。小部分差得太远的"刺头",单独放溢出表。
原理就这么朴素。难的是工程:全 Triton 内核实时解码,单层 20 微秒,168 层全部由 GPU 上的自定义内核扛下来,没有一行 PyTorch 后处理。
我为了这"一个比特都不差"的承诺,修了三个 Triton 内核的 bug:符号扩展、跨字边界、位重解释——每一个都足以让权重悄悄变错。
最终成绩:推理 105 毫秒,显存 2.0GB,困惑度 56.02,和原始一模一样。
完美的代价是:它只省了 2 倍,因为它在和"信息论"较劲——真正零冗余的压缩,下限就在那。
但如果你想要更大的空间收益呢?那就轮到第二剑了。
第二剑·实用主义:DG 4-bit,把模型塞进四分之一
第二剑,叫 DyadicGumbel 4-bit。名字很拗口,拆开看就懂了:Dyadic,二进制;Gumbel,一种让"选择"可以学习的技术。
它把每个权重压到 4 比特——理论上是 bf16 的四分之一。168 层、6.79 亿个权重,全部量化。显存从 2.2GB 直接掉到 1.4GB,打包之后磁盘上更夸张:一个 2GB 的模型,能被塞进 170MB 左右。
代价呢?诚实说:有。零样本直接量化,困惑度从 56.02 涨到 61.50,大概损失 10%。
但这个故事的关键词不是"损失",是"可微调"。
和传统量化"一刀切"不同,DG 的码本在训练阶段是可以跟着权重一起学的——每个权重组共享一个可学习的尺度,用 Gumbel-softmax 软赋值,前向走硬编码、和部署完全一致,反向走真梯度。所以它有后悔药:微调几步,精度就能往回收。
这就像一个厨师:先让菜变难吃一点点,但他告诉你——调料还能调回来。
不过,为了让这个"后悔药"真的生效,我差点把模型训成乱码。这个故事,值得单独讲。
翻车现场:一个梯度公式,把模型干成 2237
DG 4-bit 的开发过程里,有一个非常典型的"自信翻车"。
第一版 QAT 微调,用纯 Python 写软赋值——逻辑对,但慢,慢到训练没法忍。
于是我想:上 Triton 内核。把距离计算、softmax、硬编码,全塞进两个融合内核,数学上和 Python 版本完全等价,还能拿到闭式反向传播。内核写好了,训练启动了。
然后,困惑度开始失控。
零样本 78 还算正常,微调之后不但没降,反而一路冲到了2237。
2237 是什么概念?模型已经不是"变笨"了,是"嘴里开始喷乱码"。我盯着 loss 曲线看了很久,第一反应是学习率炸了,第二反应是数据坏了,第三反应才是——我的梯度公式,在 Triton 里写错了。
数学上等价,实现上不等价:闭式求导里有一个项被我简化掉了,Python 的自动求导能兜底,手写内核不会。而更讽刺的是,这个 bug 在训练初期完全不显眼——loss 还在缓慢下降,你甚至会以为自己在进步。
这就是低比特量化的恐怖之处:它失败得很"礼貌"。
修法也很憋屈:v3 回退到 Python 直通估计,温度从 5 退火到 0.3,跑 1000 步。慢,但稳。
从 2237 回到正常,靠的不是聪明,是承认"我不如自动求导"。
第三剑·探索主义:TDM-PTQ,没跑通的那把剑
第三套方案,TDM-PTQ——时分复用加训练后量化。
它的想法很性感:让每个权重组在不同的时间片里轮流"醒着",配合量化,把压缩率再往上顶一层。
结果呢?没跑通。
我在仓库里老老实实写着:TDM-PTQ,探索中,24 层的架构需要重新设计。
为什么不删掉它?因为负向结果是最便宜的研究。它告诉后来的人:这条路,24 层的结构走不通,别在这上面浪费三个月。这比我写三千字"为什么我的方案很厉害"值钱得多。
有人会问:没跑通的东西,也放进项目里?放进。就像地图上标注"此路不通"——这不是失败,这是给所有赶路人的礼物。
到这儿,三把剑都出鞘了。现在,该看那张总结表了。
单元6 | 一张表,看懂三种压缩哲学
RTX 4090,MiniCPM5-1B,同一份权重,三种方案:
| 方案 | 推理 | 显存 | 困惑度 | 质量 | 状态 |
|---|---|---|---|---|---|
| bf16 原始 | 36ms | 2.2GB | 56.02 | 100% | — |
| BF16X 无损 | 105ms | 2.0GB | 56.02 | 100% 无损 | ✅ |
| BF16X CPU流式 | 128ms | 0.9GB | 56.02 | 100% 无损 | ✅ |
| DG 4-bit | 112ms | 1.4GB | 61.50 | +10% 可微调 | ✅ |
| DG 4-bit QAT | — | — | 微调中 | 恢复中 | 🔄 |
| TDM-PTQ | — | — | — | — | 🔄 重构 |
注意最后一列:没有一行写着"已放弃"。完美的在跑,实用的在调,没跑通的在等。
这三行,就是工程世界真实的日常。
没有银弹:怎么选,比选什么更重要
把三套方案放在一起,你会看到一个反直觉的事实:省显存最多、压缩最狠的,反而不是那个"无损"的。
BF16X 追求完美,但它赢的是"可信"——交付、审计、逐比特一致,这是它的战场。
DG 4-bit 追求实用,它赢的是"空间"——端侧设备、冷启动、下载流量,这是它的主场。
TDM-PTQ 还在路上,但它赢的是"上限"——如果有一天成了,前面两把剑都会被重新洗牌。
而那个 2237 的翻车,也在提醒所有做量化的人:当你手写任何"加速"时,先问一句——它和我的自动求导,真的等价吗?
很多教程会告诉你"量化就该选 4bit"。但真实的世界没有标准答案,只有条件答案:你在意比特,还是在意比特位?你要交付,还是要装进手机?
完美主义者的妥协,不是放弃完美——是知道在哪一步妥协。
结尾
写这个仓库的时候,我给自己定了一个规矩:每个方案,都要诚实。
无损的,我把三个 bug 原原本本写进文档;有损的,我把 +10% 写在结果表第一行;没跑通的,我连"为什么失败"都写了理由。
因为我慢慢想明白一件事:做技术分享,最值钱的不是"我成功了",而是"我试过了,这是结果,包括坏的那部分"。
2.16GB 的权重,三种压缩哲学,一张诚实的成绩单。
如果你也在做模型部署,或者正纠结"无损还是有损"——先别急着选边站。问问自己:你的模型,接下来要住在哪里?你的时间,要花在完美上,还是空间上?
评论区聊聊:如果你是那 2.16GB,你愿意住进 1GB 的无损之家,还是 170MB 的四倍压缩小屋?
https://www.modelscope.cn/models/dfytensor/MiniCPM5-1B-DG-INT4