欢迎光临
我们一直在努力

AI推理优化:AWQ 量化原理,激活感知量化如何保住 4-bit 精度

一、引言:4-bit 量化的"精度塌方"是怎么发生的

把 70B 模型塞进一张 24G 显卡,甚至跑在笔记本上——这件事的命门就是量化。但凡做过 Q4 部署的人都踩过坑:GPTQ 在有些层直接"暴毙",困惑度(perplexity)飙升,输出开始胡言乱语。

问题出在哪?传统量化(RTN、GPTQ)把权重矩阵里所有元素一视同仁地压到 4-bit。但 Transformer 的权重根本不是均匀分布的——少数"敏感权重"对输出质量影响极大,把它们和"无关紧要"的权重一起粗量化,等于把房子的承重墙拆了去补院墙。

MIT Han 实验室 2023 年提出的 AWQ(Activation-aware Weight Quantization,激活感知权重量化) 给出了一条更聪明的路:看激活值,决定保哪些权重。本文带你从数学到源码,把它的核心机制拆干净。

二、核心思想:不是所有权重都值得"保真"

AWQ 的观察非常朴素但深刻:权重的重要性,取决于它对应通道的激活值大小。

  • 某个输出通道的激活(activation)越大 → 流过它的权重越"敏感" → 这些权重一旦被粗量化,误差会被放大传播。

  • 反之,激活小的通道,权重粗一点也无所谓。

于是一个关键操作诞生:对"重要通道"做 per-channel 缩放(scale up),使其数值范围变大,从而在量化网格上占据更多"刻度",等效提高了这些权重的量化精度。被放大后的通道,再配合一个对应的反缩放(在激活侧除回去),数学上完全等价于原计算,却不损失精度。

AWQ 不直接"保护"敏感权重(那样会破坏硬件对齐),而是用缩放把保护藏在计算等价变换里。

三、数学原理:一次等价变换

设线性层 Y = X · W,其中 X 是激活,W 是权重。AWQ 引入一个对角缩放矩阵 S = diag(s):

Y = X · W
= X · S · S⁻¹ · W
= (X · S) · (S⁻¹ · W)

令 W' = S⁻¹ · W(缩放后的权重,拿去量化),X' = X · S(激活侧同步缩放)。 量化后:

Ŷ = (X · S) · Q(S⁻¹ · W) ≈ Y   (只有 W' 被量化,等价性由 S⁻¹ 保证)

关键:s 只对约 1% 的"显著通道" 取大于 1 的值(如 2~8 倍),其余通道 s=1 不变。这样硬件上仍然是对整张矩阵做统一 4-bit 量化,但那 1% 的关键权重"变相"得到了更多精度预算。

四、源码走读:AWQ 到底改了哪几处

以官方 autoawq / llm-awq 实现为例,核心逻辑集中在 awq/quantization/auto_scale.py 与 awq/quantization/utils.py。三个函数串起整条链路:

1. get_weight_scale(weight, q_group_size)

按组(group)统计权重的"尺度",为后续缩放提供依据:

def get_weight_scale(weight, q_group_size=-1):
   if q_group_size == -1:                     # per-channel
       return weight.abs().max(dim=1).values.float()
   else:                                      # per-group
       weight = weight.view(-1, q_group_size)
       return weight.abs().max(dim=1).values.float()

2. get_act_scale(x) —— 注意这里是"激活"而非"权重"

AWQ 的精髓:用输入的激活幅度决定缩放:

def get_act_scale(x):
   # x: [batch, tokens, hidden]
   return x.abs().view(-1, x.shape[-1]).mean(dim=0)  # 沿 token 维取平均激活

3. auto_scale_block —— 算出对角缩放矩阵 S

通过"让权重尺度 ≈ 激活尺度"的反推,得到每个通道的 s,只放大激活大的通道:

# 简化版核心逻辑
scale = (act_scale.pow(alpha) / weight_scale.pow(alpha)).clamp(min=1e-4)
# alpha 通常取 0.5;scale>1 的通道即"显著通道"

4. apply_scale + pseudo_quant —— 先变换再量化

apply_scale(module, scales)        # W' = S⁻¹·W,并同步改后续层偏置
pseudo_quant(model, …)           # 对 W' 做 per-group 4-bit 伪量化

五、实操:三步用 AutoAWQ 量化一个模型

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
​
model_id = "Qwen/Qwen2.5-7B-Instruct"
model = AutoAWQForCausalLM.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
​
# 1) 校准:用 128 条样本统计激活(决定哪些通道是"敏感的")
model.quantize(tokenizer, quant_config={
   "w_bit": 4, "q_group_size": 128, "zero_point": True, "version": "GEMM"
}, calib_data="pileval")
​
# 2) 保存量化权重(含缩放信息)
model.save_quantized("qwen2.5-7b-awq-4bit")
tokenizer.save_pretrained("qwen2.5-7b-awq-4bit")
​
# 3) 推理:vLLM / llama.cpp / transformers 均可加载

实测对比(Qwen2.5-7B,4-bit,group=128):

方案模型体积WikiText2 PPL单卡可跑
FP16 原始 14.9 GB 6.12 需 24G+
GPTQ-4bit 4.1 GB 6.85 ✅
AWQ-4bit 4.1 GB 6.34 ✅

AWQ 在不增加体积的前提下,把 PPL 拉回了更接近 FP16 的水平——这在长文本生成、代码、数学推理上体感明显。

六、踩坑清单

  • 校准集要"像"真实输入:用领域语料做 calib,比随便抓 128 条通用文本精度高一大截。

  • q_group_size 别贪小:128 是精度/速度的甜点;64 更准但 GEMM 内核支持有限。

  • lm_head 默认跳过量化:输出层对生成质量极敏感,AWQ 会自动保留,别手贱去量化它。

  • 反缩放要成对出现:改了权重 W' 必须同步改下游 bias,否则数值会飘。

  • 七、结论与互动

    AWQ 的聪明之处,是用一次"数学上完全等价"的缩放,把量化预算精准投给那 1% 的决定性权重——这正是端侧部署能跑通大模型的前提。


    下一篇:用 Flink 把工业传感器数据流实时做成异常检测,承接本篇的"端侧→云端"数据闭环。

    赞(0)
    未经允许不得转载:171主机测评 » AI推理优化:AWQ 量化原理,激活感知量化如何保住 4-bit 精度
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址