面试题:模型量化怎么省显存?GPTQ、AWQ、GGUF 有什么区别、精度掉多少?
这是「每日一题」专栏的第十篇。每天一道面试题,后端 + AI 混合路线,从原理到代码一次讲清。上一篇讲了 秒杀系统,今天按轮换回到 AI/LLM 方向,聊上一篇预告的题:模型量化。
题目
大模型推理面试里和 KV Cache 并列的高频题:
GPTQ、AWQ、GGUF 都是量化,它们到底有什么区别?INT8 和 INT4 各能省多少显存、精度掉多少?什么时候该用哪种?
这道题考察三件事:懂不懂量化的数学本质、会不会算显存和精度账、知不知道主流方案各自的设计动机。很多人能背出「GPTQ 是量化、GGUF 是格式」,但一被追问「AWQ 为什么比 GPTQ 快还更稳」就卡壳。
先给结论,再拆开讲。
| 方案 | 类型 | 核心思路 | 精度控制手段 |
|---|---|---|---|
| GPTQ | 量化算法(PTQ) | 逐层逐列量化,用 Hessian 二阶信息补偿误差 | 误差补偿 + 校准集 |
| AWQ | 量化算法(PTQ) | 按激活分布找重要权重通道,缩放保护 | 通道级缩放,不需二阶矩阵 |
| GGUF | 文件格式(不是算法) | 打包权重和元数据,内置 k-quants 混合位宽 | 不同张量用不同位宽 |
一、量化到底在做什么
先讲数学本质。FP16 每个权重占 2 字节(16 bit),INT4 每个占 0.5 字节(4 bit)——把权重从 16 个 bit 压到 4 到 8 个 bit,显存和带宽立刻省一半到四分之三。这就是量化最直接的收益。
但「省」不是白来的:bit 少了,能表示的数值就少了。INT4 只有 16 个取值,怎么把 FP16 的权重塞进去?靠缩放和取整:
q = round(w / s) + z
w = 原始权重(FP16)
s = scale,缩放因子
z = zero point,零点偏移
q = 量化后的整数
反量化就是逆运算:w ≈ (q - z) × s。误差来自 round() 这一步——量化本质是有损压缩,所有方案比拼的都是「同样位宽下,怎么让这个损失最小」。
量化对象有三种:
| 量化对象 | 量化谁 | 收益 | 风险 |
|---|---|---|---|
| 权重量化 | 模型权重 W | 省显存、省带宽,最安全 | 低 |
| 激活量化 | 前向传播的中间激活 | 省计算(INT8 矩阵乘用 Tensor Core) | 高,激活分布动态变化 |
| KV Cache 量化 | 注意力缓存的 K/V | 省长上下文显存 | 中 |
面试时先分清这个维度,再说具体方案,立刻显得你懂行。
量化粒度也很关键:per-tensor(整个矩阵一个 scale)、per-channel(每行一个 scale)、group(比如每 128 个权重一组一个 scale)。粒度越细,误差越小,但 scale 的存储开销和计算开销越大。现代方案基本都做到 group 级别,group size 128 是主流默认值。
二、GPTQ:用二阶信息给量化「找补」
GPTQ 出身学术路线,是 Optimal Brain Quantization(OBQ)的加速版,OBQ 又源于 Optimal Brain Surgeon(OBS)——用**损失函数的 Hessian 矩阵(二阶导数)**判断「砍掉哪个权重损失最小」的老思路。
直觉是这样的:量化一个权重必然引入误差,但误差不是均匀分布的——有些方向的误差影响大,有些方向影响小。GPTQ 逐列量化权重,每量化一列,就用 Hessian 信息算出「这个误差主要会砸在哪些维度上」,然后把这笔误差补偿到对输出影响最小的维度里(数学上就是解一个最小二乘问题)。
原始权重 W → 量化 W_q(引入误差 E)
补偿:把 E 按 Hessian 投影到低影响方向,加到还没量化的权重上
→ 整体输出误差被大幅抵消
几个面试要点:
- PTQ(训练后量化):不需要重新训练,只需要一小部分校准数据(几百条文本)在模型上跑一遍,记录激活的 Hessian。
- 逐层处理:一次只处理一个线性层,量化完这一层再下一层,避免误差跨层累积。
- 为什么适合大模型:校准成本低(几分钟到几十分钟),7B 到 70B 都能跑。
- 代价:需要 GPU 跑校准,实现复杂(有 Hessian 求逆),对小模型效果一般——模型越小,权重冗余越少,误差越难找补。
三、AWQ:激活说了算,不再看权重本身
AWQ(Activation-aware Weight Quantization)换了一个视角,来自一个很关键的观察:权重的重要性不是由权重大小决定的,而是由激活大小决定的。同一个权重,如果它对应的激活值一直很大(salient channel),量化它造成的输出误差就大;如果激活值小,误差就小。
于是 AWQ 的思路变成两步:
- 找重要通道:在少量校准数据上统计每个通道的激活幅度,找出占比极少(大约 1%)但贡献巨大的 salient channels。
- 缩放保护:不直接对这些通道特殊处理(那样实现复杂),而是给每个通道乘一个缩放因子(数学上等价于把量化 scale 放大),让重要通道在量化时吃到的 round 误差相对更小。
原始权重 W → 乘通道缩放 s(保护重要通道)→ 再量化
反量化时除回 s,输出几乎不变
AWQ 相比 GPTQ 的优势,面试里是加分项:
| 对比维度 | GPTQ | AWQ |
|---|---|---|
| 原理依据 | 权重二阶信息(Hessian) | 激活分布统计 |
| 校准开销 | 需要算 Hessian,较慢 | 只统计激活幅度,快得多 |
| 实现复杂度 | 高(矩阵求逆) | 低(纯缩放) |
| 泛化性 | 对分布偏移敏感 | 对多模态、指令微调模型更稳 |
| 代表应用 | GPTQ 工具链 | vLLM 内置 AWQ、TensorRT-LLM |
一句话记忆:GPTQ 是「权重犯的错,用数学找补」,AWQ 是「先看哪些权重不能动,再保护它们」。
四、GGUF:它根本不是量化算法
这是最多人搞混的点。GGUF 是 llama.cpp 定义的一种模型文件格式——把权重、tokenizer、超参数、量化参数打包成一个文件,方便 CPU 和边缘设备加载。它不是量化方法,但它内置了一套量化方案叫 k-quants。
k-quants 的精髓是混合位宽:
| 量化名 | 含义 | 典型用途 |
|---|---|---|
| Q4_K_M | 中等 4-bit 混合量化 | 默认选择,性价比最高 |
| Q5_K_M | 5-bit 混合 | 精度敏感场景 |
| Q8_0 | 8-bit,每块一个 scale | 接近无损 |
| Q2_K | 2-bit 极限压缩 | 显存极小但质量明显下降 |
「混合」体现在:重要张量(attention 的 q/k/v 投影、输出层)用更高位宽,不重要的(FFN 中间层)用更低的 bit,块大小也比普通 group 更灵活。所以同样的 4-bit,GGUF 的 Q4_K_M 通常比朴素 INT4 效果好。
GGUF 的意义在于生态:llama.cpp / Ollama 直接跑,没有 GPU 也能推理,量化好的模型文件(比如 HuggingFace 上的 4-bit 版本)开箱即用。
五、算账:显存省多少、精度掉多少
以 LLaMA-2-7B(70 亿参数)为例:
| 方案 | 每参数位数 | 权重大小 | 相对 FP16 |
|---|---|---|---|
| FP16 | 16 bit | 约 13 GB | 1 倍 |
| INT8 | 8 bit | 约 7 GB | 一半 |
| INT4(group 128) | 4 bit | 约 3.5 到 4 GB | 四分之一 |
| GGUF Q4_K_M | 混合 4 到 6 bit | 约 4 GB | 约三分之一 |
精度上,业界公认的经验值(以困惑度/Wikitext 为例):
- INT8:几乎无损,困惑度上升约 0.1,绝大多数场景无感。
- INT4(group size 128):困惑度上升约 0.3 到 0.5,日常对话几乎无感,代码、数学等敏感任务能感觉到轻微退化。
- INT4(group size 32):粒度更细,困惑度上升可压到 0.2 以内,但推理变慢(scale 计算开销大)。
- Q2/Q3:困惑度上升超过 1,一般只用于显存实在放不下的场景。
两个容易被追问的点:
为什么 4-bit 掉精度没想象中严重? 因为量化误差是「随机噪声」性质的,模型输出的概率分布被扰动一点,argmax 出来的 token 往往不变;而且现代模型(尤其是大模型)有大量冗余参数,对权重扰动天然鲁棒。模型越大,量化越「耐造」。
KV Cache 量化呢? 上一篇讲过 KV Cache 占显存大头,所以现在推理引擎普遍支持把 KV Cache 量化到 INT8(几乎无损)甚至 INT4(长上下文场景收益巨大),和权重量化叠加使用。
六、面试回答模板
按这个顺序答,逻辑闭环:
- 先给本质:量化是用更少的 bit 表示权重,靠 scale + round 实现,是有损压缩,所有方案都在比「怎么少掉精度」。
- 再给方案:GPTQ 用 Hessian 二阶信息做误差补偿;AWQ 按激活分布保护重要通道;GGUF 是文件格式加 k-quants 混合位宽。
- 最后给选型:
| 场景 | 推荐 |
|---|---|
| 服务端高吞吐推理(vLLM/TensorRT) | AWQ 或 GPTQ INT4,配 KV Cache INT8 |
| CPU / 边缘 / 本地跑 | GGUF Q4_K_M(llama.cpp/Ollama) |
| 精度敏感(代码生成、数学) | INT8 或 Q5/Q6,别上 Q2 |
| 显存极度紧张 | INT4 加 KV Cache INT4,接受精度轻微下降 |
收尾金句:量化不是「精度换显存」的单选题,而是「用最少的精度代价换最多的显存收益」的工程题——选型的本质,是搞清楚你的模型哪些权重碰不得。
明日预告:后端/分布式方向 ——「先更新数据库还是先删缓存?Cache-Aside 到底哪里会出错、怎么保证最终一致?」