首页/知识库/LLM 量化技术实战:从 FP16 到 INT4 的压缩艺术

LLM 量化技术实战:从 FP16 到 INT4 的压缩艺术

✍️ AI Master📅 创建 2026-04-13📖 24 min 阅读
💡

文章摘要

系统梳理 LLM 量化的核心方法——PTQ、QAT、GPTQ、AWQ、SmoothQuant、GGUF,掌握 INT8/INT4 精度选择的决策框架和生产部署实践

1为什么 LLM 需要量化?内存墙与成本危机

大语言模型的参数量呈指数级增长——从 GPT-3 的 1750 亿到 GPT-4 的万亿级参数。一个 FP16 精度(半精度浮点)的 70B 模型需要 140GB 显存,而一个 FP16 的 7B 模型也需要 14GB。这意味着在消费级 GPU(通常 8-24GB)上运行稍大一些的模型几乎是不可能的。

量化(Quantization)就是解决这个问题的核心技术。它的基本思想很简单:用更少的比特数来表示模型的权重和激活值。从 FP16(16 位)到 INT8(8 位),内存减半;到 INT4(4 位),内存缩减到原来的 1/4。对于一个 70B 模型来说,INT4 量化后只需约 35GB 显存——从"完全无法加载"变成"两张消费级显卡就能跑"。

但量化不是简单的"降低精度"。模型权重中不同参数的重要性差异巨大——有些权重对输出影响很大(异常值,Outliers),有些几乎无关紧要。好的量化方法能够智能地识别哪些参数需要高精度、哪些可以压缩,从而在精度损失最小化的同时实现最大的压缩比。

2024-2025 年是 LLM 量化技术爆发的年份。GPTQ、AWQ、SmoothQuant、bitsandbytes 的 NF4 格式、以及 llama.cpp 的 K-quants 体系,构成了一个完整的量化工具链。理解它们的区别和适用场景,是每个 AI 工程师的必修课。

2量化的基础:PTQ 与 QAT 两大范式

LLM 量化有两种主要范式:训练后量化(Post-Training Quantization, PTQ)和量化感知训练(Quantization-Aware Training, QAT)。选择哪种范式取决于你的资源、时间和精度要求。

PTQ 是最常用的方式。它不需要重新训练模型,只需要一个小型校准数据集(通常几百到几千个样本)。量化过程分析模型在校准数据上的激活分布,确定每个层的缩放因子(Scale)和零点(Zero Point),然后将权重和激活值映射到更低的精度。PTQ 的优势是快速(几分钟到几小时)且无需 GPU 集群;缺点是在极低精度(如 INT2)下精度损失较大。

QAT 则在训练过程中模拟量化效应。模型在训练时就使用量化后的权重进行前向传播,但用浮点梯度进行反向传播(通过直通估计器,Straight-Through Estimator)。这样模型在训练过程中就"适应"了量化带来的精度损失。QAT 的精度通常优于 PTQ,尤其是在 INT4 及更低的精度下;但代价是需要完整的训练基础设施和大量的计算资源。

静态量化 vs 动态量化:PTQ 内部又分为两种。静态量化(Static Quantization)在量化时就确定了激活值的范围,推理时直接使用该范围;动态量化(Dynamic Quantization)在推理时根据实际输入动态计算激活范围。静态量化推理更快(不需要运行时计算范围),但对校准数据的选择更敏感;动态量化适应性更强,但推理开销稍大。

python
ptq_vs_qat.py
# PTQ vs QAT 对比示例
import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM, BitsAndBytesConfig

# ===== 方法 1:PTQ - 使用 bitsandbytes 进行 INT4 量化 =====
bnb_config_int4 = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",      # 归一化浮点 4-bit
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,  # 双重量化,进一步压缩
)

model_4bit = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3-8B",
    quantization_config=bnb_config_int4,
    device_map="auto",
)
# 优点:无需训练,直接加载量化后的模型
# 缺点:精度略低于 QAT

# ===== 方法 2:QAT - 量化感知训练 =====
class QuantizedModel(nn.Module):
    """量化感知训练的简化示例"""
    def __init__(self, base_model):
        super().__init__()
        # 插入伪量化节点(FakeQuantize)
        # 训练时模拟量化效果,推理时应用真实量化
        self.model = base_model
        self._insert_fake_quantizers()
    
    def _insert_fake_quantizers(self):
        for name, module in self.model.named_modules():
            if isinstance(module, nn.Linear):
                # 为权重和激活添加 FakeQuantize
                module.weight_fake_quant = torch.ao.nn.quantized.FakeQuantize(
                    observer=torch.ao.quantization.MinMaxObserver(),
                    quant_min=-128, quant_max=127,
                    dtype=torch.qint8, qscheme=torch.per_tensor_symmetric
                )

# QAT 训练流程:
# 1. 加载预训练模型
# 2. 插入 FakeQuantize 节点
# 3. 用校准数据集微调(通常 100-1000 步)
# 4. 导出量化模型(torch.quantization.convert)

print("PTQ 适合快速部署,QAT 适合追求极致精度")
方法需要训练?精度损失速度适用精度

PTQ - 静态

否(只需校准)

小(INT8)/ 中(INT4)

最快

INT8, INT4

PTQ - 动态

INT8

QAT

是(完整微调)

极小

中等

INT4, INT2

QLoRA

是(LoRA微调)

中等

INT4 + LoRA

3GPTQ:后训练量化的黄金标准

GPTQ(Generative Pre-trained Transformer Quantization)是 2023 年提出的 PTQ 算法,迅速成为 INT4 量化的事实标准。它的核心洞察是:LLM 的权重矩阵具有冗余结构,可以通过逐层的独立量化来实现接近 QAT 的精度,而不需要任何训练。

GPTQ 的算法流程非常优雅。对于模型中的每一层线性层(Linear Layer),GPTQ 执行以下步骤:首先,在一个校准数据集上收集该层的激活输入;然后,通过近似二阶优化(基于 Hessian 矩阵的逆)来确定最佳的量化方案;最关键的是,GPTQ 会逐元素地量化权重——每量化一个权重,就更新剩余权重的量化误差补偿,确保误差最小化。

这种"逐元素 + 误差补偿"的策略是 GPTQ 的核心优势。相比于简单地将每个权重量化到最近的 INT4 值(Round-to-Nearest),GPTQ 通过动态补偿量化误差,在 INT4 精度下通常只损失 1-3% 的困惑度(Perplexity)。

GPTQ 的局限在于量化速度——对一个 70B 模型进行 GPTQ 量化需要数小时和一张高端 GPU。但对于生产部署来说,这是一次性开销,之后的推理速度和内存节省带来的回报远超量化成本。

2025 年的重要进展包括GPTQ-Marlin——针对 NVIDIA 张量核心的推理内核优化,使 GPTQ INT4 模型的推理速度接近 FP16 的 2-3 倍;以及Meta's SpinQuant——用旋转矩阵预处理权重,使 GPTQ 在极低比特(如 INT2)下的表现显著提升。

bash
gptq_quantize.sh
# 使用 AutoGPTQ 量化模型
# 安装:pip install auto-gptq optimum

# Step 1: 下载模型
python -c "
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('meta-llama/Llama-3-8B')
tokenizer.save_pretrained('./llama-3-8b')
"

# Step 2: 执行 GPTQ 量化(需要 ~20GB VRAM)
python -c "
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer

model_name_or_path = 'meta-llama/Llama-3-8B'
quantized_model_dir = 'llama-3-8b-gptq-int4'

quantize_config = BaseQuantizeConfig(
    bits=4,                # INT4 量化
    group_size=128,        # 分组大小,128 是最佳平衡点
    desc_act=False,        # 是否使用描述性激活(设为 False 加速推理)
)

# 加载模型
model = AutoGPTQForCausalLM.from_pretrained(
    model_name_or_path, quantize_config
)

# 准备校准数据(只需要 128 个样本)
from datasets import load_dataset
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
calibration_data = load_dataset('wikitext', 'wikitext-2-raw-v1', split='train')
examples = [tokenizer(text, return_tensors='pt') for text in calibration_data['text'][:128]]

# 执行量化
model.quantize(examples, batch_size=4)

# 保存量化模型
model.save_quantized(quantized_model_dir)
print(f'量化模型已保存到 {quantized_model_dir}')
"

# Step 3: 加载量化后的模型进行推理
python -c "
from auto_gptq import AutoGPTQForCausalLM
from transformers import AutoTokenizer

model = AutoGPTQForCausalLM.from_quantized(
    'llama-3-8b-gptq-int4',
    device='cuda:0',
    use_triton=True,  # 使用 Triton 加速
)
tokenizer = AutoTokenizer.from_pretrained('llama-3-8b-gptq-int4')

inputs = tokenizer('解释一下联邦学习的核心思想', return_tensors='pt').to('cuda')
output = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(output[0], skip_special_tokens=True))
"

💡 GPTQ 参数调优:group_size 是关键参数。group_size=128 在精度和速度之间取得了最佳平衡;group_size=64 精度更好但推理稍慢;group_size=-1(per-channel)精度最好但需要更多显存。对于大多数场景,128 是默认选择。

4AWQ:激活感知权重量化

AWQ(Activation-aware Weight Quantization)是另一个明星级 PTQ 算法。它与 GPTQ 的思路不同:不是通过复杂的优化来最小化量化误差,而是先找到模型中最重要的权重——那些对激活值影响最大的权重——然后保护它们不被过度量化。

核心洞察:在 Transformer 中,约 0.1%-1% 的权重通道(Channel)对激活值的影响远超其他权重。这些"突出通道"(Salient Channels)在量化时如果精度损失太大,会显著降低模型质量。AWQ 的做法是:对这些突出通道使用更高的精度(如 FP16),对其余通道使用 INT4。

AWQ 的实现细节:首先通过校准数据分析激活分布,识别突出通道;然后对每个线性层的权重进行"激活感知"的量化——在量化之前,先将突出通道对应的权重进行缩放(Scaling),使得量化后的误差分布更均匀。这个缩放因子在推理时会被补偿回来,所以不需要额外的计算开销。

AWQ vs GPTQ:AWQ 的量化速度更快(因为它不需要 GPTQ 那样的逐元素优化),但在某些任务上精度略低于 GPTQ。两者的选择可以简化为:追求速度选 AWQ,追求精度选 GPTQ。在实际中,差异通常在 1-2% 以内,所以也可以根据工具链的兼容性来决定。

值得注意的是,AWQ 和 GPTQ 并不是互斥的。2025 年出现的 QuaRot 将 AWQ 的激活感知思想与旋转量化结合,在 INT4 精度下实现了接近 FP16 的性能。

python
awq_quantize.py
# 使用 AWQ 量化模型
# 安装:pip install autoawq

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "meta-llama/Llama-3-8B"
quant_path = "llama-3-8b-awq-int4"

# 加载模型和分词器
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 配置量化参数
quant_config = {
    "zero_point": True,     # 是否使用零点
    "q_group_size": 128,    # 分组大小(与 GPTQ 类似)
    "w_bit": 4,            # 4-bit 量化
    "version": "GEMM",      # 推理内核(GEMM 或 GEMV)
}

# 准备校准数据
calib_data = [
    "The quick brown fox jumps over the lazy dog.",
    "Machine learning is a subset of artificial intelligence.",
    # ... 更多校准文本(通常 128 条)
]

# 执行量化
model.quantize(tokenizer, quant_config=quant_config, calib_data=calib_data)

# 保存
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

# ===== 推理 =====
from awq import AutoAWQForCausalLM

model = AutoAWQForCausalLM.from_quantized(
    quant_path,
    device_map="auto",
    fuse_layers=True,     # 融合层,加速推理
)

prompt = "解释一下 AWQ 量化的核心思想"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
output = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(output[0], skip_special_tokens=True))

# 内存对比
# FP16:  ~16GB
# AWQ INT4: ~4.5GB(含推理开销)
# 压缩比: ~3.5x

5SmoothQuant:解决激活异常值

SmoothQuant 提出了一个不同的思路:不是直接量化权重,而是先"平滑"激活值中的异常值,使激活分布更均匀,从而让量化变得更容易。

问题背景:在 LLM 中,激活值(Activation)的分布通常包含极端异常值——某些 token 的激活值比其他 token 大几个数量级。这些异常值使得激活量化(Activation Quantization)变得极其困难,因为量化范围必须覆盖最大值,导致大部分正常值的量化精度很低。

SmoothQuant 的巧妙方案:在 Transformer 的每一层中,将激活值的异常值"转移"到对应的权重上。具体来说,对激活值除以一个缩放因子(使其变小),同时对权重乘以相同的缩放因子(使其变大)。由于权重的异常值通常不那么极端,这种转移使得激活值和权重都变得更容易量化。

SmoothQuant 的最大优势:它支持 W8A8 量化(权重和激活值都量化到 INT8)。相比 W8A4 或 W4A4 这样的权重-only 量化方案,W8A8 量化可以让推理引擎完全使用 INT8 张量核心(Tensor Core),从而获得最大的推理加速。

NVIDIA H100 上,SmoothQuant INT8 模型的推理速度可以达到 FP16 的 1.3-1.5 倍;如果进一步结合 TensorRT-LLM 的 FP8 支持,加速比可以达到 1.8 倍以上。

6量化格式全景:GGUF、NF4、K-quants 实战对比

在实际部署中,你面对的不仅是算法选择,还有量化格式的选择。不同的格式支持不同的硬件、不同的推理框架、以及不同的压缩策略。

GGUF(GGML Unified Format)是 llama.cpp 的模型格式,也是本地运行 LLM 的事实标准。GGUF 支持多种量化预设(Q2_K 到 Q8_0),每种预设都是精度和速度的权衡。Q4_K_M(4-bit Medium)是最受欢迎的选择——在 7B 模型上只需约 4GB 内存,质量保留约 95%。GGUF 的最大优势是跨平台——它可以在 CPU、macOS(Apple Silicon)、甚至树莓派上运行。

NF4(NormalFloat 4-bit)是 bitsandbytes 库使用的 4 位格式。它基于信息论最优的量化分布——对于正态分布的权重,NF4 比均匀的 INT4 编码多保留了约 0.3-0.5 位的信息量。NF4 是 QLoRA 的基础格式,使得在单张 24GB GPU 上微调 65B 模型成为可能。

K-quants 是 llama.cpp 在 2024 年推出的改进量化方案。它的核心思想是:不同层的量化敏感度不同。K-quants 对关键层(如注意力输出层、FFN 输出层)使用更高的精度,对不敏感的层使用更低的精度。例如,Q4_K_M 实际上是"混合精度"——部分层用 4-bit,部分层用 5-bit 或 6-bit,整体平均约 4.2 bits/weight。

选择指南:(1) 本地运行/消费级硬件 → GGUF Q4_K_M;(2) 服务器 GPU 部署 → GPTQ/AWQ INT4;(3) 需要微调 → bitsandbytes NF4 + QLoRA;(4) 追求极致推理速度 → SmoothQuant W8A8 + TensorRT-LLM;(5) Apple Silicon Mac → GGUF(Metal 加速)。

格式典型大小 (7B)质量保留最佳场景推理框架

GGUF Q8_0

7.2 GB

~99%

CPU 推理

llama.cpp

GGUF Q6_K

5.2 GB

~98%

质量优先

llama.cpp

GGUF Q4_K_M

3.9 GB

~95%

通用本地运行

llama.cpp

GGUF Q3_K_M

3.0 GB

~92%

内存受限

llama.cpp

GPTQ INT4

3.5 GB

~96%

GPU 部署

AutoGPTQ/vLLM

AWQ INT4

3.8 GB

~96%

GPU 部署

AutoAWQ/vLLM

NF4 (bnb)

3.5 GB

~97%

QLoRA 微调

bitsandbytes

FP8

7.0 GB

~98%

H100 部署

TensorRT-LLM

⚠️ 量化质量陷阱:不要仅凭困惑度(Perplexity)判断量化质量。INT4 模型可能在困惑度上与 FP16 相近,但在代码生成、数学推理等复杂任务上表现明显下降。始终在你的目标任务上评估量化后的模型。

7生产部署决策:量化方案选择与监控

在生产环境中部署量化模型,不仅仅是"选一个算法、跑一次量化"那么简单。你需要建立一套完整的量化评估、部署和监控体系。

决策框架:首先明确你的约束条件。显存预算是多少?延迟要求是什么?精度损失可接受范围多大?是否需要微调能力?这些问题的答案直接决定了你的量化方案。对于大多数场景,从 INT8 开始是最安全的选择——INT8 的精度损失通常不到 1%,且几乎所有推理引擎都支持。如果显存仍然不够,再考虑 INT4。

评估流程:量化后,必须进行系统的评估。使用你的业务数据集(而不是通用 benchmark)测试量化模型的质量。对于对话应用,可以使用 LLM-as-a-Judge 的方式,让一个 FP16 的强模型对量化模型和 FP16 模型的输出进行打分。对于分类或抽取任务,直接比较准确率/F1。

监控要点:量化模型的监控与传统模型不同。除了常规的延迟、吞吐量、错误率之外,还需要关注量化退化(Quantization Degradation)——模型在特定输入分布上的质量是否会随时间下降。这是因为量化模型对输入分布的变化更加敏感。如果发现量化模型在某种类型的 query 上持续表现不佳,可能需要针对该类型数据进行 QAT 重新量化。

混合精度策略是 2025-2026 年的重要趋势:不对整个模型使用统一的量化精度,而是对不同的层使用不同的精度。例如,嵌入层和最后一层使用 FP16,中间层使用 INT4。这种策略在精度和内存之间取得了更精细的平衡。vLLM 和 TensorRT-LLM 都已经支持混合精度推理。

QLoRA 是当前最实用的量化 + 微调方案。它使用 NF4 量化加载基座模型,然后在量化模型上训练 LoRA 适配器。这使得在单张 24GB GPU 上微调 30B+ 模型成为现实,且质量接近全精度微调。2026 年,QLoRA 已成为个人研究者和小型团队微调大模型的标准做法。

python
quantized_deployment.py
# 生产环境量化模型部署模板
import time
from transformers import AutoTokenizer, AutoModelForCausalLM
from vllm import LLM, SamplingParams

class QuantizedLLMService:
    """量化 LLM 生产服务"""
    
    def __init__(
        self,
        model_path: str,
        quantization: str = "gptq",  # gptq, awq, fp8
        gpu_memory_utilization: float = 0.9,
        max_model_len: int = 4096,
    ):
        # 使用 vLLM 部署量化模型
        self.llm = LLM(
            model=model_path,
            quantization=quantization,
            gpu_memory_utilization=gpu_memory_utilization,
            max_model_len=max_model_len,
            tensor_parallel_size=1,  # 多卡时调整
        )
        self.tokenizer = AutoTokenizer.from_pretrained(model_path)
        self.sampling_params = SamplingParams(
            temperature=0.7,
            top_p=0.9,
            max_tokens=512,
        )
        
        # 性能监控
        self.request_count = 0
        self.total_latency = 0.0
    
    def generate(self, prompt: str) -> dict:
        """生成响应并记录性能指标"""
        start_time = time.time()
        
        outputs = self.llm.generate(
            [prompt], 
            self.sampling_params,
            use_tqdm=False
        )
        
        latency = time.time() - start_time
        self.request_count += 1
        self.total_latency += latency
        
        result = outputs[0].outputs[0].text
        tokens = outputs[0].outputs[0].token_ids
        
        return {
            "text": result,
            "tokens_generated": len(tokens),
            "latency_ms": latency * 1000,
            "tokens_per_sec": len(tokens) / latency,
        }
    
    def get_metrics(self) -> dict:
        """获取服务性能指标"""
        avg_latency = (
            self.total_latency / self.request_count 
            if self.request_count > 0 else 0
        )
        return {
            "total_requests": self.request_count,
            "avg_latency_ms": avg_latency * 1000,
        }


# ===== 量化方案决策辅助 =====
def choose_quantization(
    vram_gb: int,
    model_size_b: int,
    need_finetuning: bool = False,
    quality_priority: bool = False,
) -> str:
    """根据约束条件推荐量化方案"""
    fp16_vram = model_size_b * 2  # FP16 需要 ~2GB/1B参数
    
    if fp16_vram <= vram_gb and not quality_priority:
        return "fp16"  # 显存够用,不量化
    
    ratio = vram_gb / fp16_vram
    
    if ratio >= 0.5:
        return "int8"      # INT8(2x 压缩)
    elif need_finetuning:
        return "nf4_qlora" # QLoRA 微调
    elif quality_priority:
        return "gptq_int4" # GPTQ INT4(精度最优)
    else:
        return "awq_int4"  # AWQ INT4(速度最优)

# 使用示例
scheme = choose_quantization(
    vram_gb=24, model_size_b=30, 
    need_finetuning=True
)
print(f"推荐方案: {scheme}")  # → nf4_qlora

💡 从 INT8 开始:在大多数生产场景中,INT8 量化提供了最佳的性价比——2 倍内存节省、1-3 倍推理加速、不到 1% 的精度损失。只有在 INT8 仍然无法满足显存需求时,才考虑 INT4。

继续你的 AI 学习之旅

浏览更多 AI 知识库文章,或者探索 GitHub 上的优质 AI 项目