前言:大模型太"胖"了
大模型的参数量越来越大:
| 模型 | 参数量 | FP16 显存 | 普通人能跑吗? |
|---|---|---|---|
| LLaMA-7B | 7B | 14 GB | ⚠️ 勉强 |
| LLaMA-13B | 13B | 26 GB | ❌ 困难 |
| LLaMA-70B | 70B | 140 GB | ❌ 不可能 |
| GPT-4 | ~1.8T? | ~3.6 TB | 🤣 做梦 |
问题很明显:模型太大,显存不够。
解决方案:量化(Quantization)——用更少的 bit 来存储参数。
一、量化基础:用更少的 bit 表示数字
1.1 什么是量化?
量化就是用低精度数据类型代替高精度数据类型。
# 原始:FP16(16 bit 浮点数)
weight_fp16 = 0.123456789 # 精确
# 量化后:INT8(8 bit 整数)
weight_int8 = 31 # 近似,需要配合缩放因子
# 还原:31 / 255 ≈ 0.1216
# 更激进:INT4(4 bit 整数)
weight_int4 = 2 # 更粗糙的近似
# 只有 16 个可能的值(0-15)1.2 数据类型对比
范围大,精度高
4 字节/参数"] FP16["FP16 (16 bit)
训练常用
2 字节/参数"] BF16["BF16 (16 bit)
范围=FP32,精度低
2 字节/参数"] INT8["INT8 (8 bit)
256 个值
1 字节/参数"] INT4["INT4 (4 bit)
16 个值
0.5 字节/参数"] end FP32 --> FP16 --> INT8 --> INT4
| 类型 | 位数 | 范围 | 精度 | 用途 |
|---|---|---|---|---|
| FP32 | 32 | ±3.4e38 | 高 | 训练(优化器状态) |
| FP16 | 16 | ±65504 | 中 | 训练/推理 |
| BF16 | 16 | ±3.4e38 | 低 | 训练(更稳定) |
| INT8 | 8 | -128~127 | 低 | 推理量化 |
| INT4 | 4 | -8~7 或 0~15 | 很低 | 激进量化 |
1.3 量化的数学原理
线性量化:将浮点数映射到整数
$$ x_q = \text{round}\left(\frac{x}{s}\right) + z $$
其中:
- $x$:原始浮点数
- $x_q$:量化后的整数
- $s$:缩放因子(scale)
- $z$:零点(zero point)
反量化(还原):
$$ x \approx s \cdot (x_q - z) $$
import torch
def quantize_tensor(tensor, num_bits=8):
"""将张量量化到指定位数"""
# 计算范围
qmin = 0
qmax = 2 ** num_bits - 1
# 计算缩放因子和零点
min_val, max_val = tensor.min(), tensor.max()
scale = (max_val - min_val) / (qmax - qmin)
zero_point = qmin - min_val / scale
zero_point = torch.clamp(torch.round(zero_point), qmin, qmax)
# 量化
q_tensor = torch.clamp(
torch.round(tensor / scale + zero_point),
qmin, qmax
).to(torch.uint8)
return q_tensor, scale, zero_point
def dequantize_tensor(q_tensor, scale, zero_point):
"""反量化"""
return scale * (q_tensor.float() - zero_point)
# 示例
original = torch.randn(1000) * 0.1
quantized, scale, zp = quantize_tensor(original, num_bits=8)
restored = dequantize_tensor(quantized, scale, zp)
# 计算误差
error = (original - restored).abs().mean()
print(f"平均量化误差: {error:.6f}")
# 输出类似: 平均量化误差: 0.0001561.4 量化粒度
量化可以在不同粒度上进行:
整个张量一个 scale"] PC["Per-Channel
每个通道一个 scale"] PG["Per-Group
每 N 个元素一个 scale"] PT2["Per-Token
每个 token 一个 scale"] end PT -->|精度| Low[低] PG -->|精度| High[高] PT -->|开销| Small[小] PG -->|开销| Large[大]
| 粒度 | 精度 | 额外存储 | 适用场景 |
|---|---|---|---|
| Per-Tensor | 最低 | 最少 | 简单量化 |
| Per-Channel | 中等 | 中等 | CNN |
| Per-Group | 较高 | 较多 | LLM 推荐 |
| Per-Token | 最高 | 最多 | 激活值 |
Per-Group 量化是 LLM 量化的主流选择,通常 group_size=128。
二、LLM 量化方法全景
2.1 量化方法分类
2.2 主流方法对比
| 方法 | 位数 | 精度损失 | 速度 | 适用场景 |
|---|---|---|---|---|
| GPTQ | 4/8 bit | 小 | 快 | GPU 推理 |
| AWQ | 4 bit | 最小 | 快 | GPU 推理 |
| GGUF | 2-8 bit | 中等 | 中 | CPU/混合推理 |
| bitsandbytes | 4/8 bit | 小 | 中 | 训练+推理 |
| SmoothQuant | 8 bit | 很小 | 最快 | 服务部署 |
2.3 各方法的技术特点
三、GPTQ:基于 Hessian 的量化
3.1 核心思想
GPTQ 的核心洞察:量化误差可以通过调整未量化的权重来补偿。
基于 Optimal Brain Quantization (OBQ) 算法,使用 Hessian 矩阵来决定量化顺序和误差补偿。
3.2 数学原理
目标:最小化量化后的输出误差
$$ \min_{\hat{W}} ||WX - \hat{W}X||^2 $$
Hessian 矩阵:
$$ H = 2 X X^T $$
量化第 $i$ 列时的最优误差补偿:
$$ \delta_j = -\frac{w_i - \hat{w}_i}{H_{ii}} H_{ij} $$
3.3 GPTQ 实践
from transformers import AutoModelForCausalLM, AutoTokenizer
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
def quantize_with_gptq(
model_name: str,
output_dir: str,
bits: int = 4,
group_size: int = 128,
):
"""使用 GPTQ 量化模型"""
# 量化配置
quantize_config = BaseQuantizeConfig(
bits=bits, # 量化位数
group_size=group_size, # 分组大小
desc_act=True, # 激活值降序处理
damp_percent=0.01, # 阻尼系数
)
# 加载模型
model = AutoGPTQForCausalLM.from_pretrained(
model_name,
quantize_config=quantize_config,
torch_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 准备校准数据
calibration_data = [
"The quick brown fox jumps over the lazy dog.",
"Machine learning is transforming the world.",
"Large language models are very powerful.",
# ... 更多校准样本
]
# 编码校准数据
examples = [
tokenizer(text, return_tensors="pt")
for text in calibration_data
]
# 执行量化
model.quantize(examples)
# 保存量化模型
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"量化完成!保存到 {output_dir}")
return model
def load_gptq_model(model_path: str):
"""加载 GPTQ 量化模型"""
model = AutoGPTQForCausalLM.from_quantized(
model_path,
device_map="auto",
use_safetensors=True,
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
return model, tokenizer
# 使用示例
if __name__ == "__main__":
# 量化
quantize_with_gptq(
model_name="meta-llama/Llama-2-7b-hf",
output_dir="./llama2-7b-gptq-4bit",
bits=4,
group_size=128,
)
# 加载使用
model, tokenizer = load_gptq_model("./llama2-7b-gptq-4bit")
# 推理
inputs = tokenizer("Hello, how are you?", return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0]))3.4 使用 TheBloke 的预量化模型
TheBloke 在 HuggingFace 上提供了大量预量化模型:
from transformers import AutoModelForCausalLM, AutoTokenizer
# 直接加载预量化模型
model_name = "TheBloke/Llama-2-7B-GPTQ"
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 使用
inputs = tokenizer("What is machine learning?", return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))四、AWQ:激活感知量化
4.1 核心思想
AWQ 发现:只有约 1% 的权重通道对模型输出影响巨大。
策略:识别这些重要通道,保护它们的精度。
4.2 为什么激活感知很重要?
传统量化只看权重分布,但忽略了一个关键问题:
# 假设两个权重通道
channel_1 = [0.001, 0.002, 0.001] # 权重小
channel_2 = [0.1, 0.2, 0.1] # 权重大
# 传统量化:按权重大小分配精度
# channel_2 看起来更"重要"
# 但是!如果激活值分布是这样的:
activation_1 = 1000 # 激活值大
activation_2 = 1 # 激活值小
# 实际输出:
output_1 = channel_1 * activation_1 = [1, 2, 1] # 贡献大!
output_2 = channel_2 * activation_2 = [0.1, 0.2, 0.1] # 贡献小
# AWQ:根据 weight × activation 判断重要性4.3 AWQ 量化公式
对于权重 $W$ 和激活 $X$,AWQ 寻找最优的缩放因子 $s$:
$$ \min_s ||Q(W \cdot s) \cdot (X / s) - W \cdot X||^2 $$
重要通道(激活值大的)使用更大的 $s$,从而在量化后保持更高精度。
4.4 AWQ 实践
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
def quantize_with_awq(
model_name: str,
output_dir: str,
bits: int = 4,
group_size: int = 128,
):
"""使用 AWQ 量化模型"""
# 加载模型
model = AutoAWQForCausalLM.from_pretrained(
model_name,
safetensors=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 量化配置
quant_config = {
"zero_point": True,
"q_group_size": group_size,
"w_bit": bits,
"version": "GEMM", # GEMM 或 GEMV
}
# 执行量化
model.quantize(
tokenizer,
quant_config=quant_config,
)
# 保存
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"AWQ 量化完成!保存到 {output_dir}")
def load_awq_model(model_path: str):
"""加载 AWQ 模型"""
from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_quantized(
model_path,
fuse_layers=True, # 融合层以加速
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
return model, tokenizer
# 使用 vLLM 加载 AWQ 模型(推荐)
def use_awq_with_vllm(model_path: str):
"""使用 vLLM 加载 AWQ 模型获得最佳性能"""
from vllm import LLM, SamplingParams
llm = LLM(
model=model_path,
quantization="awq",
dtype="half",
)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=100,
)
outputs = llm.generate(["What is deep learning?"], sampling_params)
for output in outputs:
print(output.outputs[0].text)
# 使用示例
if __name__ == "__main__":
# 量化
quantize_with_awq(
model_name="meta-llama/Llama-2-7b-hf",
output_dir="./llama2-7b-awq-4bit",
)
# 加载
model, tokenizer = load_awq_model("./llama2-7b-awq-4bit")4.5 GPTQ vs AWQ
| 维度 | GPTQ | AWQ |
|---|---|---|
| 核心思想 | Hessian 误差补偿 | 激活感知保护 |
| 量化速度 | 较慢 | 较快 |
| 推理速度 | 快 | 更快 |
| 精度保持 | 好 | 更好 |
| vLLM 支持 | ✅ | ✅ 更优 |
| 推荐度 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
结论:新项目推荐使用 AWQ,与 vLLM 配合效果最佳。
五、GGUF:CPU 推理的王者
5.1 什么是 GGUF?
GGUF(GPT-Generated Unified Format) 是 llama.cpp 使用的模型格式,专为 CPU 推理优化。
前身是 GGML,2023 年升级为 GGUF,支持更多功能。
5.2 GGUF 量化级别
GGUF 提供多种量化级别,命名规则如 Q4_K_M:
| 量化类型 | 位数 | 大小(7B) | 质量 | 速度 |
|---|---|---|---|---|
| Q2_K | 2-3 bit | ~3 GB | 差 | 最快 |
| Q3_K_S | 3 bit | ~3.5 GB | 较差 | 很快 |
| Q3_K_M | 3 bit | ~3.8 GB | 一般 | 很快 |
| Q4_0 | 4 bit | ~4 GB | 一般 | 快 |
| Q4_K_M | 4 bit | ~4.5 GB | 推荐 | 快 |
| Q5_K_M | 5 bit | ~5 GB | 好 | 中等 |
| Q6_K | 6 bit | ~6 GB | 很好 | 较慢 |
| Q8_0 | 8 bit | ~8 GB | 最好 | 慢 |
命名规则解释:
Q4:4 bit 量化K:使用 K-Quant 方法(更好的精度)M:Medium 配置(S=Small, M=Medium, L=Large)
5.3 转换模型为 GGUF
# 1. 克隆 llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
# 2. 编译
make -j
# 3. 下载模型
# 从 HuggingFace 下载原始模型到 ./models/llama-7b/
# 4. 转换为 GGUF
python convert.py ./models/llama-7b/ --outtype f16 --outfile ./models/llama-7b-f16.gguf
# 5. 量化
./quantize ./models/llama-7b-f16.gguf ./models/llama-7b-q4_k_m.gguf Q4_K_M5.4 使用 llama-cpp-python
from llama_cpp import Llama
def use_gguf_model(model_path: str):
"""使用 GGUF 模型"""
# 加载模型
llm = Llama(
model_path=model_path,
n_ctx=2048, # 上下文长度
n_threads=8, # CPU 线程数
n_gpu_layers=0, # GPU 层数(0=纯 CPU)
verbose=False,
)
# 生成
output = llm(
"What is machine learning?",
max_tokens=100,
temperature=0.7,
stop=["Q:", "\n\n"],
)
print(output["choices"][0]["text"])
return llm
def use_gguf_with_gpu(model_path: str, n_gpu_layers: int = 35):
"""混合 CPU/GPU 推理"""
llm = Llama(
model_path=model_path,
n_ctx=4096,
n_threads=4,
n_gpu_layers=n_gpu_layers, # 部分层放 GPU
verbose=True,
)
return llm
# Chat 模式
def chat_with_gguf(model_path: str):
"""对话模式"""
llm = Llama(
model_path=model_path,
n_ctx=2048,
chat_format="llama-2", # 指定对话格式
)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello! How are you?"},
]
response = llm.create_chat_completion(
messages=messages,
max_tokens=100,
temperature=0.7,
)
print(response["choices"][0]["message"]["content"])
# 使用示例
if __name__ == "__main__":
model_path = "./models/llama-2-7b-chat.Q4_K_M.gguf"
# 基本使用
llm = use_gguf_model(model_path)
# 对话
chat_with_gguf(model_path)5.5 使用 Ollama(最简单)
Ollama 封装了 llama.cpp,提供最简单的使用方式:
# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 运行模型(自动下载)
ollama run llama2
# 或者指定量化版本
ollama run llama2:7b-q4_K_M
# 列出已下载的模型
ollama list
# 使用 API
curl http://localhost:11434/api/generate -d '{
"model": "llama2",
"prompt": "What is machine learning?"
}'Python 调用 Ollama:
import ollama
def use_ollama():
"""使用 Ollama API"""
# 生成
response = ollama.generate(
model="llama2",
prompt="What is machine learning?",
)
print(response["response"])
# 对话
response = ollama.chat(
model="llama2",
messages=[
{"role": "user", "content": "Hello!"},
],
)
print(response["message"]["content"])
# 流式输出
for chunk in ollama.chat(
model="llama2",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True,
):
print(chunk["message"]["content"], end="", flush=True)
# 使用自定义 GGUF
def use_custom_gguf_with_ollama():
"""使用自定义 GGUF 文件"""
# 创建 Modelfile
modelfile = """
FROM ./my-model.Q4_K_M.gguf
TEMPLATE \"\"\"[INST] {{ .Prompt }} [/INST]\"\"\"
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
"""
with open("Modelfile", "w") as f:
f.write(modelfile)
# 创建模型
# ollama create my-model -f Modelfile
# 运行
# ollama run my-model六、bitsandbytes:训练时量化
6.1 特点
bitsandbytes 是 HuggingFace 生态的量化库,特点是:
- 支持训练时量化(QLoRA 就用它)
- 与 transformers 无缝集成
- 支持 8-bit 和 4-bit
6.2 8-bit 量化推理
from transformers import AutoModelForCausalLM, AutoTokenizer
def load_8bit_model(model_name: str):
"""8-bit 量化加载"""
model = AutoModelForCausalLM.from_pretrained(
model_name,
load_in_8bit=True,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
return model, tokenizer
# 使用
model, tokenizer = load_8bit_model("meta-llama/Llama-2-7b-hf")
inputs = tokenizer("Hello, how are you?", return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0]))6.3 4-bit 量化(NF4)
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
def load_4bit_model(model_name: str):
"""4-bit NF4 量化加载"""
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4 量化
bnb_4bit_compute_dtype=torch.bfloat16, # 计算类型
bnb_4bit_use_double_quant=True, # 双重量化
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
return model, tokenizer
# 显存对比
def compare_memory():
"""对比不同量化方式的显存"""
import gc
model_name = "meta-llama/Llama-2-7b-hf"
configs = [
("FP16", {"torch_dtype": torch.float16}),
("8-bit", {"load_in_8bit": True}),
("4-bit", {"load_in_4bit": True}),
]
for name, kwargs in configs:
gc.collect()
torch.cuda.empty_cache()
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto",
**kwargs,
)
memory = torch.cuda.max_memory_allocated() / 1024**3
print(f"{name}: {memory:.1f} GB")
del model
gc.collect()
torch.cuda.empty_cache()
# 输出类似:
# FP16: 13.5 GB
# 8-bit: 7.2 GB
# 4-bit: 4.1 GB6.4 量化 + LoRA 训练(QLoRA)
上一篇已详细介绍,这里补充完整代码:
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
import torch
def train_qlora(
model_name: str,
dataset,
output_dir: str,
):
"""完整 QLoRA 训练流程"""
# 1. 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
# 2. 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
# 3. 准备训练
model = prepare_model_for_kbit_training(model)
# 4. LoRA 配置
lora_config = LoraConfig(
r=64,
lora_alpha=128,
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# 5. 训练
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
bf16=True,
optim="paged_adamw_8bit",
gradient_checkpointing=True,
)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()
trainer.save_model()七、量化效果对比
7.1 精度 vs 显存 vs 速度
精度: ⭐⭐⭐⭐⭐
显存: 14GB
速度: 基准"] INT8["INT8
精度: ⭐⭐⭐⭐
显存: 7GB
速度: 1.5x"] INT4["INT4 (GPTQ/AWQ)
精度: ⭐⭐⭐⭐
显存: 4GB
速度: 2x"] Q4KM["Q4_K_M (GGUF)
精度: ⭐⭐⭐
显存: 4.5GB
速度: CPU友好"] end
7.2 Benchmark 数据
以 LLaMA-2-7B 为例(数据仅供参考):
| 量化方式 | 显存 | Perplexity | 推理速度 |
|---|---|---|---|
| FP16 | 13.5 GB | 5.47 | 1x |
| GPTQ 4-bit | 4.2 GB | 5.52 | 1.8x |
| AWQ 4-bit | 4.2 GB | 5.50 | 2.1x |
| GGUF Q4_K_M | 4.5 GB | 5.58 | 1.5x (CPU) |
| bnb 4-bit | 4.1 GB | 5.55 | 1.3x |
关键发现:
- 4-bit 量化精度损失很小(~1%)
- AWQ 在速度和精度上都优于 GPTQ
- GGUF 在 CPU 上性能最好
7.3 如何选择?
简单建议:
- GPU 部署:AWQ + vLLM
- CPU/本地:GGUF + Ollama
- 训练:bitsandbytes (QLoRA)
八、实战:端到端量化流程
8.1 GPU 部署方案(AWQ + vLLM)
# Step 1: 量化
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model = AutoAWQForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
model.quantize(tokenizer, quant_config={"w_bit": 4, "q_group_size": 128})
model.save_quantized("./llama2-7b-awq")
tokenizer.save_pretrained("./llama2-7b-awq")
# Step 2: 使用 vLLM 部署
from vllm import LLM, SamplingParams
llm = LLM(
model="./llama2-7b-awq",
quantization="awq",
dtype="half",
max_model_len=4096,
)
sampling_params = SamplingParams(temperature=0.7, max_tokens=100)
outputs = llm.generate(["Hello, how are you?"], sampling_params)
for output in outputs:
print(output.outputs[0].text)
# Step 3: 启动 API 服务
# python -m vllm.entrypoints.openai.api_server \
# --model ./llama2-7b-awq \
# --quantization awq \
# --port 80008.2 本地部署方案(GGUF + Ollama)
# Step 1: 下载或转换 GGUF
# 从 HuggingFace 下载预转换的 GGUF
# https://huggingface.co/TheBloke/Llama-2-7B-GGUF
# Step 2: 创建 Modelfile
cat > Modelfile << 'EOF'
FROM ./llama-2-7b.Q4_K_M.gguf
TEMPLATE """[INST] <<SYS>>
You are a helpful assistant.
<</SYS>>
{{ .Prompt }} [/INST]"""
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
PARAMETER stop "[INST]"
PARAMETER stop "[/INST]"
EOF
# Step 3: 创建并运行
ollama create my-llama -f Modelfile
ollama run my-llama
# Step 4: API 调用
curl http://localhost:11434/api/generate -d '{
"model": "my-llama",
"prompt": "What is quantum computing?",
"stream": false
}'8.3 Python 完整示例
"""
完整的量化部署示例
支持 GPTQ、AWQ、GGUF 三种方式
"""
import torch
from typing import Optional
class QuantizedModelLoader:
"""统一的量化模型加载器"""
@staticmethod
def load_gptq(model_path: str, device: str = "auto"):
"""加载 GPTQ 模型"""
from auto_gptq import AutoGPTQForCausalLM
from transformers import AutoTokenizer
model = AutoGPTQForCausalLM.from_quantized(
model_path,
device_map=device,
use_safetensors=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
return model, tokenizer
@staticmethod
def load_awq(model_path: str, device: str = "auto"):
"""加载 AWQ 模型"""
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model = AutoAWQForCausalLM.from_quantized(
model_path,
fuse_layers=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
return model, tokenizer
@staticmethod
def load_gguf(model_path: str, n_gpu_layers: int = 0):
"""加载 GGUF 模型"""
from llama_cpp import Llama
model = Llama(
model_path=model_path,
n_ctx=4096,
n_gpu_layers=n_gpu_layers,
verbose=False,
)
return model, None
@staticmethod
def load_bnb_4bit(model_name: str):
"""加载 bitsandbytes 4-bit 模型"""
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
return model, tokenizer
def generate_text(
model,
tokenizer,
prompt: str,
max_tokens: int = 100,
temperature: float = 0.7,
model_type: str = "hf", # "hf" 或 "gguf"
):
"""统一的文本生成接口"""
if model_type == "gguf":
# GGUF 模型
output = model(
prompt,
max_tokens=max_tokens,
temperature=temperature,
)
return output["choices"][0]["text"]
else:
# HuggingFace 模型
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=max_tokens,
temperature=temperature,
do_sample=True,
)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
# 使用示例
if __name__ == "__main__":
# 选择加载方式
loader = QuantizedModelLoader()
# 方式 1: GPTQ
# model, tokenizer = loader.load_gptq("TheBloke/Llama-2-7B-GPTQ")
# text = generate_text(model, tokenizer, "Hello!", model_type="hf")
# 方式 2: AWQ
# model, tokenizer = loader.load_awq("./llama2-7b-awq")
# text = generate_text(model, tokenizer, "Hello!", model_type="hf")
# 方式 3: GGUF
# model, _ = loader.load_gguf("./llama-2-7b.Q4_K_M.gguf")
# text = generate_text(model, None, "Hello!", model_type="gguf")
# 方式 4: bitsandbytes
model, tokenizer = loader.load_bnb_4bit("meta-llama/Llama-2-7b-hf")
text = generate_text(model, tokenizer, "What is AI?", model_type="hf")
print(text)九、总结
量化方法速查表
关键 Takeaway
- 量化是必备技能:让大模型在消费级硬件上运行
- 4-bit 量化精度损失很小:通常 <1%,完全可用
选择建议:
- GPU 部署:AWQ + vLLM
- CPU/本地:GGUF + Ollama
- 训练:bitsandbytes (QLoRA)
- GGUF 命名规则:Q4_K_M 推荐,平衡精度和大小
显存需求:
- 7B FP16: 14GB → 4-bit: 4GB
- 70B FP16: 140GB → 4-bit: 35GB
推荐工具
| 场景 | 工具 | 推荐度 |
|---|---|---|
| GPU 量化 | AutoAWQ | ⭐⭐⭐⭐⭐ |
| GPU 部署 | vLLM | ⭐⭐⭐⭐⭐ |
| CPU/本地 | Ollama | ⭐⭐⭐⭐⭐ |
| 训练 | bitsandbytes | ⭐⭐⭐⭐⭐ |
| 模型转换 | llama.cpp | ⭐⭐⭐⭐ |
下一步学习
- [ ] vLLM 推理加速:极致性能
- [ ] 本地部署实战:Ollama 完全指南
- [ ] 模型压缩:知识蒸馏
参考资料
- GPTQ Paper - GPTQ: Accurate Post-Training Quantization
- AWQ Paper - AWQ: Activation-aware Weight Quantization
- llama.cpp - GGUF 格式的发源地
- bitsandbytes - 8-bit/4-bit 量化库
- Ollama - 最简单的本地部署方案