大模型的低成本应用--量化
摘要: 大模型量化通过将高精度浮点数(如FP32/FP16)转换为低精度表示(如Int8/Int4),显著减少显存占用、加速推理并降低能耗。主流方法包括: GPTQ:基于二阶误差最小化的逐层量化,适合GPU推理; AWQ:保护关键权重,量化次要权重,平衡精度与效率; Bitsandbytes:支持QLoRA微调,优化8/4位计算; GGUF:面向CPU的跨平台格式,支持灵活硬件卸载。 工具如ms-
大模型的量化(Quantization)概念与作用
概念
量化本质上是将模型中的高精度浮点数(如32位浮点数 FP32 或16位浮点数 FP16/BF16,通常用于表示模型的权重和激活值)转换为低精度表示(如8位整数 Int8、4位整数 Int4 甚至更低)的过程。
这个转换是通过应用一个缩放因子(Scale)和零点(Zero Point)来实现的,将高精度的浮点范围映射到低精度的整数范围。
核心作用
量化带来的好处主要集中在以下三个方面:
-
显著减少显存(内存)占用:将FP16(2字节)的权重转换为Int4(0.5字节)或Int8(1字节),可以将模型大小缩小 4× 到 2×。这是量化最直接也最关键的作用,使得在VRAM有限的GPU(如家用显卡)上运行大型模型成为可能。
-
加速推理:低精度的整数运算通常比浮点运算更快,尤其是在针对整数运算优化的硬件(如某些GPU核心或CPU)上。
-
降低能耗:更少的内存访问和更快的计算速度自然也带来了更低的能耗。
挑战
量化是一种有损压缩。降低数值精度会引入量化误差(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。 |
文件后缀 |
通常是 |
通常是 |
在 PyTorch 代码中配置使用。 |
|
详细说明
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 在其完整的模型生命周期中,将量化作为**“后训练过程”的一个重要环节。它本身不发明新的量化算法,而是整合了当前最主流、最有效的量化方法**,提供统一的接口供用户调用。
-
整合主流算法支持:
-
ms-swift 支持将模型导出为使用 GPTQ、AWQ 和 Bitsandbytes (BNB) 等技术量化后的格式。
-
它还支持更前沿的量化技术,例如 AQLM、HQQ 和 EETQ。
-
-
量化训练(QLoRA/Quantization-Aware Fine-Tuning):
-
ms-swift 是一个微调框架,因此它原生支持基于量化的微调方法,尤其是 QLoRA (基于 BNB 的 NF4 量化),允许用户在有限的显存下对超大模型进行参数高效性微调。
-
-
部署加速:
-
ms-swift 允许将量化后的模型与 vLLM、SGLang 和 LMDeploy 等高性能推理引擎结合,从而在部署时实现更快的推理速度。
-
-
多模态支持:
-
作为支持多模态大模型的框架,ms-swift 的量化能力也延伸到 MLLM,例如可以导出 Qwen-VL 等模型的 Int4 或 Int8 量化版本。
-
总结: ms-swift 的量化价值在于其全链路、一站式的集成能力,它将多种 SOTA(State-of-the-Art,当前最佳)量化技术作为工具箱的一部分,方便用户在微调后直接进行量化并部署。
2. LLM-Compressor
LLM-Compressor 是一个由 vLLM 团队主导开发的库,目标更为集中:为部署提供优化的模型压缩解决方案,并且与 vLLM 高性能推理引擎紧密集成。
量化定位与作用:
LLM-Compressor 的核心是提供一个模块化、可组合的压缩框架,它不仅仅包括量化,还包括结构化稀疏化等其他压缩技术。
-
全面的量化算法实现:
-
它完整实现了包括 GPTQ、AWQ 和 SmoothQuant 在内的多种后训练量化(PTQ)算法。
-
SmoothQuant 在这里尤其值得一提。这是一种旨在解决激活值中异常值(Outliers)问题的量化方法,通过将激活值的量化难度转移到权重上,使得模型可以更容易地进行 W8A8 (权重8位,激活值8位) 全量化,这对实现更高的吞吐量至关重要。
-
-
灵活的压缩方案组合:
-
LLM-Compressor 采用 Modifier(修改器)的架构。用户可以定义一个“压缩策略 (Recipe)”,将不同的压缩技术串联或组合起来。例如,可以先使用
SmoothQuantModifier
平衡量化难度,再使用GPTQModifier
进行最终的量化。
-
-
支持多种精度和方案:
-
支持多种量化方案,包括:
-
W4A16 (权重4位,激活值16位):典型的仅权重量化,适用于显存受限但吞吐量要求不高的场景。
-
W8A8 (INT8 或 FP8):全量化,适用于对吞吐量有高要求的计算密集型部署场景。
-
KV Cache 量化: 针对长文本推理,对 Key-Value Cache 进行量化,以减少推理时的内存占用。
-
-
-
与 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模型释放后")
更多推荐
所有评论(0)