一聊到 Llama-4 我这种平时靠本地模型吃饭的人,第一反应不是“推理效果有多惊艳”,而是“多少卡才够用”。全精度权重动不动几十个 GB,单卡放不下,多卡又贵,最后只能把目光放到量化上。这两年我把主流量化方案基本都试了一遍,真正让我觉得“有东西”的只有 QuIP# 和 HQQ。这篇就仔细聊聊这两种方法到底在干什么,为什么它们比传统量化更适合新一代大规模模型,以及我在 Llama-4 上落地时踩过的一些坑。
如果你正准备把 Llama-4 塞进有限显存里,或者想搞清楚 4bit 以下量化为什么不只是“简单取整”,这篇文章应该能给你一个比较完整的参考。
1. 为什么Llama-4这一代必须重新审视量化
1.1 量化其实是在“用一个数替代一串数”
先别急着看算法,我们把量化这件事还原到最朴素的操作:把原来用 16bit 浮点数表示的权重,换成 4bit 或者 3bit 的整数区间。一个 70B 级别的模型,光权重大概 140GB(按 16bit 算)。如果能用 4bit,直接压缩到三分之一到四分之一,显卡的显存压力会小非常多。
很多人觉得量化就是把数字四舍五入,理论上没毛病,但实际操作远不是那么简单。权重的分布不是均匀的,某些维度可能出现极端大值,一取整就把这些异常值打没了;有些维度又特别敏感,稍微动一点,下一层的激活值就完全跑偏。所以现代量化方法都在解决同一个问题:如何在把权重压到极低比特的同时,保留关键信息。
1.2 规模变大以后,误差会被逐层放大
小模型时代,我们做 8bit 量化,精度几乎不损失,那是因为单层误差本身就小,网络深度不够,误差来不及累积。到了 Llama-4 这种超深Transformer架构,每一层都会把前一层的误差继续传递放大。你单独看某一层,量化后的输出和原来差距很小,但经过几十层叠加,最终的概率分布就会变得很差。
这也是为什么 QuIP# 和 HQQ 这类方法强调“误差可控”。它们是针对权重矩阵本身做优化,而不是像旧式 PTQ 那样只做简单的动态范围裁剪。这代模型参数量太大,容错空间反而更小,量化过程必须更精细。
1.3 部署形态决定量化方案选型
在选 QuIP# 还是 HQQ 之前,得先想清楚你的落地场景。如果是自己电脑上跑推理,通常选 4bit,服从直观的精度和速度平衡;如果要做长期服务的后端,可以考虑更激进的 3bit 或 2bit,前提是你可以接受一定的精度下降;如果模型要发布给别人用,又不想暴露太多参数细节,那无校准集的 HQQ 会更省事。
不同的部署形态不仅影响量化比特数,还会影响你是否能用校准集。有的系统本身没有跑过目标场景的数据,强行套一条开源数据做校准,结果反而更差。这时候 HQQ 的优势就出来了。
1.4 工具链成熟度同样影响效果
再强的算法,不好落地也白搭。QuIP# 因为需要旋转矩阵、LDL 分解这类复杂操作,很多时候要自己写集成代码;HQQ 实现相对简单,只要有 PyTorch 基础就能快速上手。某公司内部量化框架能把整个流程封装成几行命令,但那是别人调好的,自己一旦遇到新模型,还是得回到底层原理去排队排查。
2. QuIP#:先旋转再做低比特,信息论背书的思路
2.1 为什么直接 round 不够
直接四舍五入是最朴素的量化方式,但它的误差和权重数值大小成正比。一个权重是 3.87,量化到 4bit 网格上,误差可能很小;一个权重是 128.4,如果动态范围只覆盖到 128,你得把 4bit 的网格铺在整个范围上,那落在 3.87 附近的网格间隔会非常大,一取整就损失惨重。
Llama 系列里有很多 outlier 权重,它们数量少但值很大。直接 round 等于把宝贵的信息容量花在照顾这几个大数上,其他占比更高的普通权重的精度反而被牺牲。
2.2 随机旋转与 LDL 分解
QuIP# 的第一个关键动作是:在做量化之前,先把权重矩阵乘上一个随机正交矩阵,这个矩阵通常用 Hadamard 变换生成。旋转的目的是把原本集中在少数维度中的极端数值“打散”到所有维度去,这样每个维度上的数值范围都变得比较温和,再进行低比特量化时误差可以被控制住。
第二个动作是使用 LDL 分解来优化舍入方式。简单理解就是,在一个维度上取了整,会影响到下一个维度的最优选择,不是每个维度独立判断,而是要全局协调。QuIP# 用三角分解把这个依赖关系解耦,使得对每个维度的舍入都基于前一个维度的误差修正,效果上比独立 round 好很多。
2.3 关键参数与两比特能力
QuIP# 被讨论最多的是它能做到 2bit。在传统观念里,2bit 量化就是灾难,但 QuIP# 通过随机旋转和精确舍入,让模型在 2bit 下还有可用的困惑度表现。实际使用中需要关注几个参数:旋转矩阵的块大小 vec_size、量化比特数 bits、以及缩放因子的分组大小 group_size。
vec_size 越大,旋转的全局性越强,但计算开销也越高;group_size 越小,缩放因子的粒度越细,误差越小,但需要存储更多 scale 参数,二进制的实际收益会被慢慢吃掉。选参数的过程就是在这三件事之间找平衡。我实测下来,vec_size 选 32 或 64 比较稳,bits 选 4 时基本无感,bits 选 2 时就要配合更小的 group_size 才不至于崩。
3. HQQ:不依赖校准集的工程派方案
3.1 把量化看成最小二乘问题
HQQ 的出发点非常朴素:既然量化会带误差,那我就直接去找一组 scale 和 zero_point,让量化后的权重和原始权重的均方误差最小。这个问题本身不好直接解,因为舍入操作不可导,但 HQQ 用了一种半二次分裂的思路,把它拆成两个交替优化的步骤:先固定 scale 更新整数权重,再固定整数权重更新 scale。
这个过程不涉及反向传播,只涉及到统计量级别的迭代,所以计算量比训练一个小型适配器要低得多。最值得一提的是,它完全不需要校准数据。你只要有一个 Llama-4 权重文件,就能当场量化。
3.2 不建校准集,省掉的麻烦比想象的多
传统 PTQ 方案的流程通常是:选一条有代表性的校准数据,前向跑一遍模型,统计每层激活值的动态范围,再决定量化参数。问题出在“代表性”这三个字上。聊天场景的校准集和代码生成场景的校准集,动态范围可能完全不一样;用一套通用的校准集去量化 Llama-4,在某个垂直场景里可能性能好,换个场景又变差。
HQQ 只基于权重本身的统计特性来量化,不关心数据来自哪里。这个特性在以下几个场景非常有用:你手头只有权重,没有任何样例数据;数据涉及隐私,不能拿去跑前向过程;想要快速出一个低比特版本做对比,没时间准备精调校准集。
3.3 与 QuIP# 在效果上的取舍
HQQ 不是没有代价。因为它不做旋转,对 outlier 的处理能力就弱一些,尤其在 2bit 场景下,精度通常比不上 QuIP#。但 3bit 和 4bit 场景下,两者差距并不大,HQQ 的部署复杂度低很多,成了我在项目里优先推荐给新手的方案。
| 对比项 | QuIP# | HQQ |
|---|---|---|
| 量化原理 | 随机旋转 + LDL 优化舍入 | 半二次优化 scale 与 zero_point |
| 校准数据 | 通常不需要 | 不需要 |
| 2bit 效果 | 相对稳健 | 容易崩,慎用 |
| 实现复杂度 | 较高 | 较低 |
| 推理速度 | 受旋转算子影响 | 更快 |
| 适用场景 | 极限压缩 | 快速上手、常规压缩 |
4. 在 Llama-4 上落地:实操参数与过程记录
4.1 量化前先过一遍模型体检
拿到 Llama-4 权重之后,我不会直接开量化。先跑一个 100 条文本样本的推理,确认原模型本身输出正常。有些社区二次发行的权重,可能已经被改过 embedding 或者输出层,直接量化会把问题放大。这个动作只需要几分钟,但能避免后面排查半天以为量化代码有 bug。
另外我会检查一下模型里哪些层是浮点计算密集的。注意力层的 QKV 投影往往比 MLP 层对量化更敏感,量化时可以考虑保留 8bit,其余模块压到 4bit。这种混合精度在工程上比全模型统一 bit 更实用。
4.2 QuIP# 量化 Llama-4 的实操流程
QuIP# 在工程上会依赖稍微复杂一点的算子,我用的是基于 PyTorch 的实验性实现。核心步骤是先把每个权重矩阵转成适合旋转的维度,然后用 Hadamard 矩阵做变换,再执行优化舍入。下面是简化核心流程:
import torch import torch.nn.functional as F # 假设 model 是已经加载到 CPU 的 Llama-4 模型 # 这里只展示对单个线性层权重量化的核心思路 def quip_quantize(weight, bits=4, vec_size=32): w = weight.float() orig_shape = w.shape # 将权重重排为 (out_features, in_features) # 将其填充到 vec_size 的整数倍 pad = (-w.shape[1]) % vec_size if pad > 0: w = F.pad(w, (0, pad)) out, inp = w.shape # 随机 Hadamard 变换,需要保证 rotate 矩阵可复现 # 实用中会用一系列随机正交矩阵相乘 # 这里伪代码使用随机正交矩阵示意 R = torch.linalg.qr(torch.randn(inp, inp, dtype=torch.float32))[0] w_rot = w @ R # 执行基于 LDL 的优化舍入 # 实际算法里会遍历维度,利用历史误差调整当前维度的量化 scale = w_rot.abs().max(dim=0).values / (2**(bits-1) - 1) w_q = torch.round(w_rot / scale) # 逆旋转 w_dequant = (w_q * scale) @ R.T return w_dequant[:, :orig_shape[1]].reshape(orig_shape).to(weight.dtype)注意这里不是完整的生产实现。真正的 QuIP# 还会做旋转矩阵融合、LDL 分解误差回传、以及分组 scale 存储。但你可以看到整个思路:旋转变换、量化、逆变换。
实操时我会把每个 Linear 层替换成这个量化后的权重,然后冻结其他参数,只做 feed-forward。这里最大的坑是旋转矩阵的内存开销。如果一次加载全部旋转矩阵,Llama-4 这种超大模型的显存峰值会非常高,所以最好逐层进行旋转和量化,算子计算完就释放。
4.3 HQQ 量化 Llama-4 的实操流程
HQQ 的代码思路非常直观,不需要旋转矩阵,时间复杂度也更低。下面是一个最小可行的量化函数:
def hqq_quantize(weight, bits=4, group_size=128): w = weight.float() out_features, in_features = w.shape # 按 group_size 分组 num_groups = in_features // group_size w_reshaped = w.reshape(out_features, num_groups, group_size) min_val = w_reshaped.min(dim=-1, keepdim=True).values max_val = w_reshaped.max(dim=-1, keepdim=True).values # 先做一个初始 scale / zero_point n_levels = 2 ** bits - 1 scale = (max_val - min_val) / n_levels zero_point = min_val # 半二次迭代:更新整数,再更新 scale for _ in range(20): w_q = torch.clamp(torch.round((w_reshaped - zero_point) / scale), 0, n_levels) w_dequant = w_q * scale + zero_point # 这个例子没有真正的优化,但实际 HQQ 会在这里调整 scale 和 zero_point scale = (w_reshaped - zero_point).abs().mean(dim=-1, keepdim=True) / (n_levels / 2) w_q = torch.clamp(torch.round((w_reshaped - zero_point) / scale), 0, n_levels) return w_q.to(torch.uint8), scale, zero_point生产环境建议直接找现成的实现,不要自己写迭代逻辑。半二次优化里有个很容易出错的地方是更新顺序:必须先更新整数权重,再更新 scale,反过来会让优化发散。
HQQ 的工程优势在于量化速度快。在 Llama-4 上跑一遍全模型量化,几十分钟能完成,QuIP# 可能要数个小时。第一次验证效果时我基本都用 HQQ 快速摸底,再决定是否值得用 QuIP# 精细打磨。
4.4 效果评估不能只看困惑度
很多人量化完就报一个 PPL(困惑度)数字,然后宣称无损。但 PPL 算的是语言模型对测试文本的困惑程度,不代表真实任务上的效果。真实项目里我一般会看三组指标:
第一,同一批测试集上的 PPL 对比。这个可以快速发现问题。PPL 波动在 0.5 以内算正常,超过 1 就要警惕。
第二,一组固定下游任务的准确率。比如文本分类、代码补全、摘要生成里的 ROUGE。这些任务能反映量化对模型能力的实际影响。
第三,部署性能。包括显存峰值、单 token 延迟、吞吐量。有时候量化模型能放进显存,但推理速度反而比原版慢,那就没有实际意义。
我做过一次 Llama-4 的 3bit 对比:HQQ 的 PPL 比 QuIP# 多 0.8,但在代码生成任务上,两者表现几乎一样,原因是代码任务的 token 分布相对集中,受量化影响小。这一经历让我明白,只看 PPL 选方案容易误判,结合下游任务才可靠。
5. 常见问题与排查经验
5.1 结果忽好忽坏,先查随机种子
QuIP# 里的旋转矩阵有随机性,不同种子会生成不同舍入路径。如果你发现同一份代码跑两次,量化后模型表现不稳定,多半是旋转矩阵没有固定随机种子。实际工程中,我会将旋转矩阵生成过程固化下来,保存成一个二进制文件,所有模型复用同一个矩阵。这样可以保证实验可复现。
固定种子之后,还能快速做参数扫描。比如 vec_size 从 16 到 64,每次量化完跑一遍验证集,选 PPL 最低的那组参数。省时省力。
5.2 校准集泄漏导致 PPL 虚低
虽然 HQQ 不需要校准集,但如果你用带校准集的 PTQ 方法做对比,一定要确认验证集和校准集不是同一个来源。我自己踩过坑:用一堆 wiki 文本去校准,又用同一堆 wiki 文本去测试 PPL,结果量化模型跑出比原版还低的损失。后来换到完全独立的评测集,分数立刻变回正常。这个教训提醒我在任何量化对比实验里,评测数据都必须隔离。
5.3 量化后内存反而更高,检查计算图
如果你把量化后的模型直接丢给 PyTorch 推理,而没有先把非量化层的权重从计算图中分离,内存占用可能会爆炸。量化矩阵在反量化时会产生临时高精度张量,这些张量如果没有及时释放,就会堆积在显存里。
解决方案是使用torch.no_grad()包裹推理,并且对量化层的权重调用detach();如果显存碎片严重,可以考虑连续内存优化,或者一次性跑长序列而不是多 batch 并行。
5.4 注意力层崩了,其他层没事
有时候 PPL 增加不多,但生成文本头部经常重复,典型的注意力层量化受损。遇到这种情况,我会把 QKV 投影和输出投影单独保留为 8bit,只对 MLP 层做 3bit 或 4bit。这样显存只增加很少,但生成质量能挽回很多。
KV cache 的量化也要慎重。Llama-4 长上下文场景中,KV cache 占显存非常大,使用 8bit 量化能明显收益。但 cache 量化需要配一份精细的动态缩放策略,直接沿用权重量化的 scale 方法通常结果不佳。
5.5 2bit 的时候,先跑一小段稳定输出
任何方案在 2bit 下都不敢保证每个模型都不崩。我习惯做法是量化完先给模型一小段固定前缀,让它生成 64 个 token,检查输出是否流畅、有没有重复死循环。如果连最短生成都卡住,再回头调整 group_size,或者对部分关键层升到 4bit。
我实测下来,Llama-4 在 2bit 下 QuIP# 还能保持能看的输出,但 HQQ 需要配合偏大的 group_size 才能不崩。如果你没有精力调整,建议 2bit 别碰 HQQ。
最后分享一个实用习惯
我在量化 Llama-4 时养成一个习惯:每换一组参数,就把“量化结果摘要”写进一个文本文件,字段包括 PPL、显存、单 token 延迟、以及哪些层保留高精度。后期复盘时,这比翻聊天记录高效得多。量化不是一个一锤定音的工作,参数组合爆炸多,记录能帮你少走很多弯路。
如果你刚开始接触,我建议先从 HQQ 的 4bit 开始,把整个量化链路跑通,再尝试 QuIP# 往 2bit 挤压。毕竟工具再先进,最终看的还是你的部署场景到底需要什么。先求稳,再求极限压缩,这条路最不折腾。