大模型的量化(Quantization)概念与作用

概念

量化本质上是将模型中的高精度浮点数(如32位浮点数 FP32 或16位浮点数 FP16/BF16,通常用于表示模型的权重和激活值)转换低精度表示(如8位整数 Int8、4位整数 Int4 甚至更低)的过程。

这个转换是通过应用一个缩放因子(Scale)和零点(Zero Point)来实现的,将高精度的浮点范围映射到低精度的整数范围。

核心作用

量化带来的好处主要集中在以下三个方面:

  1. 显著减少显存(内存)占用:将FP16(2字节)的权重转换为Int4(0.5字节)或Int8(1字节),可以将模型大小缩小 4× 到 2×。这是量化最直接也最关键的作用,使得在VRAM有限的GPU(如家用显卡)上运行大型模型成为可能。

  2. 加速推理:低精度的整数运算通常比浮点运算更快,尤其是在针对整数运算优化的硬件(如某些GPU核心或CPU)上。

  3. 降低能耗:更少的内存访问和更快的计算速度自然也带来了更低的能耗。

挑战

量化是一种有损压缩。降低数值精度会引入量化误差(Quantization Error),可能导致模型精度下降(即模型输出的质量或准确性降低)。因此,各种量化方法的核心目标就是在大幅压缩模型的同时,最大限度地减少精度的损失

主流量化方法与格式对比

特性

GPTQ (Generalized Post-Training Quantization)

AWQ (Activation-Aware Weight Quantization)

Bitsandbytes (BnB)

GGUF (GGML Universal Format)

类型

量化算法/方法 (Post-Training Quantization)

量化算法/方法 (Post-Training Quantization)

量化库/方法 (尤其是 QLoRA 的基础)

文件格式 (基于 GGML 库)

主要优化目标

GPU 推理速度和低精度 (如4-bit) 下的准确性

GPU 推理,通过保护关键权重来保持准确性

GPU 显存和训练/微调(如QLoRA)。

CPU/Apple Silicon 上的高效推理和易用性

核心思路

逐层量化,使用二阶信息最小化量化误差(最小化重构误差)。

识别并保护对激活值影响最大的关键权重,只量化“不重要”的权重。

专注于 8-bit (Int8) 和 4-bit NormalFloat (NF4) 量化,提供高效的CUDA内核。

一种新的文件格式,包含了模型权重、元数据和各种k-quants(量化方法,如Q4_K、Q5_K等)。

精度

优秀,尤其在 4-bit 表现出色。

优秀,通常在相同精度下速度更快。

优秀,NF4 是 4-bit 优化的基线。

良好,提供多种不同量化等级(如Q4_K、Q5_K)供选择。

适用硬件

NVIDIA GPU

NVIDIA GPU

NVIDIA GPU

CPU(如PC、MacBook Pro/Air),并支持部分层卸载到GPU

文件后缀

通常是 .safetensors.bin 文件,但在文件名中或模型卡中标记为 *-gptq

通常是 .safetensors.bin 文件,但在文件名中或模型卡中标记为 *-awq

在 PyTorch 代码中配置使用。

.gguf

详细说明

1. GPTQ (Generalized Post-Training Quantization)
  • 特点:是一种经典的“一枪”(one-shot)后训练量化方法。它不需要重新训练模型,只需一小部分校准数据(Calibration Data)。它的目标是找到最优的量化权重,使量化后的权重与原始权重之间的误差(或重构误差)最小化。

  • 优势:在低比特率(如 4-bit)下能保持很好的模型准确性。推理速度快,是 GPU 部署 4-bit 模型的主流选择之一。

2. AWQ (Activation-Aware Weight Quantization)
  • 特点:认为并非所有权重都同等重要。它通过分析模型在校准数据集上的激活值,识别出对模型性能影响最大的“显著权重”(Salient Weights),然后不量化或仅轻微量化这些关键权重,而将大部分不重要的权重进行激进量化。

  • 优势:由于保留了关键信息,通常在相同的量化水平下,准确性损失比 GPTQ 更小推理速度也可能更快

3. Bitsandbytes (BnB)
  • 特点bitsandbytes 是一个深度学习量化库,广泛用于 Hugging Face 生态系统。它提供的 4-bit NormalFloat (NF4) 量化是 QLoRA(一种高效微调技术)的基础。它主要关注在训练和微调时减少显存占用,也用于推理。

  • 优势:是实现 QLoRA 的关键,极大地降低了微调大型模型的硬件要求。它提供高度优化的 8-bit 和 4-bit 矩阵乘法内核。

4. GGUF (GGML Universal Format)
  • 特点GGUF 不是一种量化算法,而是一种模型文件格式,是 GGML(一个用C/C++编写的、专注于CPU推理的库)的继任者。它旨在让LLM在CPU、Apple Silicon 和低端GPU上高效运行。.gguf 文件中包含各种量化方案(k-quants)的权重。

  • 优势跨平台兼容性极佳,特别是对 CPU 推理友好。用户可以通过调整n_gpu_layers参数来控制有多少层卸载到GPU,从而灵活利用硬件资源。是个人用户在笔记本电脑或普通台式机上运行LLM的首选格式。

可选的量化工具

1. ms-swift (ModelScope SWIFT)

ms-swift (Scalable lightWeight Infrastructure for Fine-Tuning) 是阿里巴巴达摩院 ModelScope 社区提供的、用于大模型和多模态大模型(MLLM)训练、微调和部署的综合性框架。

量化定位与作用:

ms-swift 在其完整的模型生命周期中,将量化作为**“后训练过程”的一个重要环节。它本身不发明新的量化算法,而是整合了当前最主流、最有效的量化方法**,提供统一的接口供用户调用。

  1. 整合主流算法支持:

    • ms-swift 支持将模型导出为使用 GPTQAWQBitsandbytes (BNB) 等技术量化后的格式。

    • 它还支持更前沿的量化技术,例如 AQLMHQQEETQ

  2. 量化训练(QLoRA/Quantization-Aware Fine-Tuning):

    • ms-swift 是一个微调框架,因此它原生支持基于量化的微调方法,尤其是 QLoRA (基于 BNB 的 NF4 量化),允许用户在有限的显存下对超大模型进行参数高效性微调。

  3. 部署加速:

    • ms-swift 允许将量化后的模型与 vLLMSGLangLMDeploy 等高性能推理引擎结合,从而在部署时实现更快的推理速度。

  4. 多模态支持:

    • 作为支持多模态大模型的框架,ms-swift 的量化能力也延伸到 MLLM,例如可以导出 Qwen-VL 等模型的 Int4 或 Int8 量化版本。

总结: ms-swift 的量化价值在于其全链路、一站式的集成能力,它将多种 SOTA(State-of-the-Art,当前最佳)量化技术作为工具箱的一部分,方便用户在微调后直接进行量化并部署。

2. LLM-Compressor

LLM-Compressor 是一个由 vLLM 团队主导开发的库,目标更为集中:为部署提供优化的模型压缩解决方案,并且与 vLLM 高性能推理引擎紧密集成。

量化定位与作用:

LLM-Compressor 的核心是提供一个模块化、可组合的压缩框架,它不仅仅包括量化,还包括结构化稀疏化等其他压缩技术。

  1. 全面的量化算法实现:

    • 它完整实现了包括 GPTQAWQSmoothQuant 在内的多种后训练量化(PTQ)算法。

    • SmoothQuant 在这里尤其值得一提。这是一种旨在解决激活值中异常值(Outliers)问题的量化方法,通过将激活值的量化难度转移到权重上,使得模型可以更容易地进行 W8A8 (权重8位,激活值8位) 全量化,这对实现更高的吞吐量至关重要。

  2. 灵活的压缩方案组合:

    • LLM-Compressor 采用 Modifier(修改器)的架构。用户可以定义一个“压缩策略 (Recipe)”,将不同的压缩技术串联或组合起来。例如,可以先使用 SmoothQuantModifier 平衡量化难度,再使用 GPTQModifier 进行最终的量化。

  3. 支持多种精度和方案:

    • 支持多种量化方案,包括:

      • W4A16 (权重4位,激活值16位):典型的仅权重量化,适用于显存受限但吞吐量要求不高的场景。

      • W8A8 (INT8 或 FP8):全量化,适用于对吞吐量有高要求的计算密集型部署场景。

      • KV Cache 量化: 针对长文本推理,对 Key-Value Cache 进行量化,以减少推理时的内存占用。

  4. 与 vLLM 深度整合:

    • 压缩后的模型以 safetensors 或其他压缩格式保存,可以直接导入 vLLM 引擎进行高效推理,充分发挥硬件的低精度计算能力。

总结: LLM-Compressor 是一个高度专业化的模型压缩工具箱,其优势在于算法的全面性、模块化和与高性能部署环境的深度集成,旨在为企业级或大规模推理提供更灵活和高效的模型优化方案。

ModelScope Notebook环境下MS-Swift模型量化示例

环境准备

!pip uninstall transformers
#确保 transformers==4.51.3,否则后面在运行Qwen3的量化时还会遇到形如 AttributeError: 'Catcher' object has no attribute 'attention_type' 的报错
!pip install transformers==4.51.3
import os
import torch
from swift.llm import export_main, ExportArguments, quantize_model
from swift.utils import get_logger

# 设置日志
logger = get_logger()

# 设置GPU设备
os.environ['CUDA_VISIBLE_DEVICES'] = '0'

# 检查CUDA可用性
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
    print(f"GPU device: {torch.cuda.get_device_name(0)}")
    print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")

配置基础参数

# 基础配置
BASE_MODEL = "Qwen/Qwen3-8B"  # 注意:如果Qwen3-8B不存在,可以使用Qwen/Qwen2.5-7B-Instruct
QUANT_BITS = 4
MAX_LENGTH = 2048
QUANT_N_SAMPLES = 256
QUANT_BATCH_SIZE = 1
GROUP_SIZE = 128
DATASET_SAMPLE_SIZE = 500  # 每个数据集使用的样本数量,可根据需要调整

# 量化数据集配置
DATASETS = [
    f'AI-ModelScope/alpaca-gpt4-data-zh#{DATASET_SAMPLE_SIZE}',
    f'AI-ModelScope/alpaca-gpt4-data-en#{DATASET_SAMPLE_SIZE}'
]

print(f"Base model: {BASE_MODEL}")
print(f"Quantization bits: {QUANT_BITS}")
print(f"Dataset sample size: {DATASET_SAMPLE_SIZE} per dataset")
print(f"Total samples: {DATASET_SAMPLE_SIZE * len(DATASETS)}")
print(f"Datasets: {DATASETS}")

1. AWQ 量化

AWQ (Activation-aware Weight Quantization) 是一种高效的权重量化方法。

def quantize_with_awq():
    """使用AWQ方法进行量化"""
    print("\n=== 开始 AWQ 量化 ===")
    
    # 创建AWQ量化参数
    awq_args = ExportArguments(
        model=BASE_MODEL,
        dataset=DATASETS,
        quant_method='awq',
        quant_bits=QUANT_BITS,
        quant_n_samples=QUANT_N_SAMPLES,
        quant_batch_size=QUANT_BATCH_SIZE,
        max_length=MAX_LENGTH,
        group_size=GROUP_SIZE,
        output_dir=f'{BASE_MODEL.split("/")[-1]}-AWQ-Int{QUANT_BITS}',
        device_map='auto',  # 自动分配设备
        safe_serialization=True,  # 使用安全序列化
    )
    
    try:
        # 方法1: 使用export_main
        export_main(awq_args)
        print(f"✅ AWQ量化完成,输出目录: {awq_args.output_dir}")
        
        # 方法2: 直接使用quantize_model (注释掉,避免重复)
        # quantize_model(awq_args)
        
    except Exception as e:
        print(f"❌ AWQ量化失败: {str(e)}")
        return None
    
    return awq_args.output_dir

# 执行AWQ量化
awq_output_dir = quantize_with_awq()

2. GPTQ 量化

GPTQ 是另一种流行的量化方法,通常在保持模型性能方面表现良好。

def quantize_with_gptq():
    """使用GPTQ方法进行量化"""
    print("\n=== 开始 GPTQ 量化 ===")
    
    # 设置环境变量(GPTQ可能需要)
    os.environ['OMP_NUM_THREADS'] = '14'
    
    # 创建GPTQ量化参数
    gptq_args = ExportArguments(
        model=BASE_MODEL,
        dataset=DATASETS,
        quant_method='gptq',
        quant_bits=QUANT_BITS,
        quant_n_samples=QUANT_N_SAMPLES,
        quant_batch_size=QUANT_BATCH_SIZE,
        max_length=MAX_LENGTH,
        group_size=GROUP_SIZE,
        output_dir=f'{BASE_MODEL.split("/")[-1]}-GPTQ-Int{QUANT_BITS}',
        device_map='auto',
        safe_serialization=True,
    )
    
    try:
        export_main(gptq_args)
        print(f"✅ GPTQ量化完成,输出目录: {gptq_args.output_dir}")
    except Exception as e:
        print(f"❌ GPTQ量化失败: {str(e)}")
        return None
    
    return gptq_args.output_dir

# 执行GPTQ量化
gptq_output_dir = quantize_with_gptq()

3. BNB (BitsAndBytes) 量化

BitsAndBytes 提供了简单易用的量化方法,特别适合快速量化。

def quantize_with_bnb():
    """使用BitsAndBytes方法进行量化"""
    print("\n=== 开始 BNB 量化 ===")
    
    # 创建BNB量化参数
    bnb_args = ExportArguments(
        model=BASE_MODEL,
        quant_method='bnb',
        quant_bits=QUANT_BITS,
        output_dir=f'{BASE_MODEL.split("/")[-1]}-BNB-Int{QUANT_BITS}',
        device_map='auto',
        safe_serialization=True,
        # 注意:BNB量化通常不需要校准数据集
    )
    
    try:
        export_main(bnb_args)
        print(f"✅ BNB量化完成,输出目录: {bnb_args.output_dir}")
    except Exception as e:
        print(f"❌ BNB量化失败: {str(e)}")
        return None
    
    return bnb_args.output_dir

# 执行BNB量化
bnb_output_dir = quantize_with_bnb()

ModelScope Notebook环境下llm-compressor模型量化示例

环境准备

截止日前ModelScope Notebook默认环境下直接安装llmcompressor会冲突报错,需要使用conda建立一个干净的环境进行下列安装
!pip install llmcompressor
# 导入必要的库
import torch
import gc  # 用于垃圾回收和显存管理
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer

from llmcompressor import oneshot
from llmcompressor.modifiers.awq import AWQModifier
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor.utils import dispatch_for_generation

# 设置随机种子以确保结果可重现
torch.manual_seed(42)
# 显存监控函数
def print_gpu_memory(stage=""):
    """打印当前GPU显存使用情况"""
    if torch.cuda.is_available():
        allocated = torch.cuda.memory_allocated() / 1024**3
        total_memory_bytes = torch.cuda.get_device_properties(torch.cuda.current_device()).total_memory
        total_memory = total_memory_bytes / 1024**3
        print(f"{stage} - 显存使用: {allocated:.2f} GB (已分配) / {total_memory:.2f} GB (总量)")
    else:
        print(f"{stage} - 未检测到CUDA设备")

# 显示初始显存状态
print_gpu_memory("初始状态")

1.模型和数据集准备

# 模型配置
MODEL_ID = "/mnt/workspace/.cache/modelscope/models/Qwen/Qwen3-8B"

# 数据集配置
DATASET_ID = "/mnt/workspace/.cache/modelscope/datasets/AI-ModelScope___alpaca-gpt4-data-zh"
DATASET_SPLIT = "train"

# 量化参数
NUM_CALIBRATION_SAMPLES = 256  # 校准样本数量,可根据需要调整
MAX_SEQUENCE_LENGTH = 2048     # 最大序列长度

print(f"准备量化模型: {MODEL_ID}")
print(f"使用数据集: {DATASET_ID}")
print(f"校准样本数: {NUM_CALIBRATION_SAMPLES}")
# 加载模型和分词器
print("正在加载模型和分词器...")
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID, 
    torch_dtype="auto",
    trust_remote_code=True,
    device_map="auto"  # 自动分配设备
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)

print(f"模型加载完成,参数量: {model.num_parameters():,}")
print(f"模型设备: {next(model.parameters()).device}")

2.准备校准数据集

# 加载和预处理数据集 (Alpaca格式)
print("正在加载校准数据集...")
ds = load_dataset(DATASET_ID, split=f"{DATASET_SPLIT}[:{NUM_CALIBRATION_SAMPLES}]")
ds = ds.shuffle(seed=42)

# 检查数据集格式
print(f"数据集列名: {ds.column_names}")
print(f"示例数据: {ds[0]}")

def preprocess(example):
    """预处理数据,将Alpaca格式转换为文本格式"""
    # Alpaca数据集格式处理
    instruction = example.get("instruction", "")
    input_text = example.get("input", "")
    output = example.get("output", "")
    
    # 构建Alpaca格式的提示
    if input_text:
        # 有输入的情况
        prompt = f"Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n"
    else:
        # 没有输入的情况
        prompt = f"Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:\n"
    
    # 添加输出
    text = prompt + output
    
    return {"text": text}

def tokenize(sample):
    """对文本进行分词"""
    return tokenizer(
        sample["text"],
        padding=False,
        max_length=MAX_SEQUENCE_LENGTH,
        truncation=True,
        add_special_tokens=False,
    )

# 应用预处理和分词
ds = ds.map(preprocess)
ds = ds.map(tokenize, remove_columns=ds.column_names)

print(f"数据集准备完成,样本数: {len(ds)}")
print(f"示例长度: {len(ds[0]['input_ids'])}")

3.原始模型推理

def test_model_generation(model, tokenizer, prompt="你好,我是", max_new_tokens=150):
    """测试模型生成能力"""
    print(f"\n测试提示: {prompt}")
    
    # 准备输入
    inputs = tokenizer(prompt, return_tensors="pt")
    inputs = {key: value.to(model.device) for key, value in inputs.items()}
    
    # 生成文本
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            do_sample=True,
            temperature=0.7,
            pad_token_id=tokenizer.eos_token_id
        )
    
    # 解码输出
    generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(f"生成结果: {generated_text}")
    return generated_text

# 测试原始模型
print("=" * 50)
print("原始模型推理测试")
print("=" * 50)
original_output = test_model_generation(model, tokenizer)

4.AWQ量化

# 创建模型副本用于AWQ量化
print("正在创建模型副本用于AWQ量化...")
awq_model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID, 
    torch_dtype="auto",
    trust_remote_code=True,
    device_map="auto"
)

# 配置AWQ量化参数
awq_recipe = AWQModifier(
    targets="Linear",           # 目标层类型
    scheme="W4A16",            # 权重4位,激活16位
    ignore=["lm_head"],        # 忽略输出层
    duo_scaling=False          # 禁用双重缩放
)

print("开始AWQ量化...")
print(f"量化方案: {awq_recipe.scheme}")
print(f"目标层: {awq_recipe.targets}")
print(f"忽略层: {awq_recipe.ignore}")
# 执行AWQ量化
oneshot(
    model=awq_model,
    dataset=ds,
    recipe=awq_recipe,
    max_seq_length=MAX_SEQUENCE_LENGTH,
    num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)

print("AWQ量化完成!")
# 清理显存
torch.cuda.empty_cache()
gc.collect()
print_gpu_memory("AWQ量化完成后")
# 测试AWQ量化后的模型
print("=" * 50)
print("AWQ量化模型推理测试")
print("=" * 50)
dispatch_for_generation(awq_model)
awq_output = test_model_generation(awq_model, tokenizer)

# 保存AWQ量化模型
AWQ_SAVE_DIR = "Qwen3-8B-AWQ-W4A16"
print(f"\n正在保存AWQ量化模型到: {AWQ_SAVE_DIR}")
awq_model.save_pretrained(AWQ_SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(AWQ_SAVE_DIR)
print("AWQ模型保存完成!")

# 释放AWQ模型显存,为GPTQ量化做准备
del awq_model
torch.cuda.empty_cache()
gc.collect()
print_gpu_memory("AWQ模型释放后")
Logo

更多推荐