搜 索

模型量化:大模型的减肥手册

  • 2阅读
  • 2025年04月26日
  • 0评论
首页 / AI/大数据 / 正文

前言:大模型太"胖"了

大模型的参数量越来越大:

模型参数量FP16 显存普通人能跑吗?
LLaMA-7B7B14 GB⚠️ 勉强
LLaMA-13B13B26 GB❌ 困难
LLaMA-70B70B140 GB❌ 不可能
GPT-4~1.8T?~3.6 TB🤣 做梦

问题很明显:模型太大,显存不够

解决方案:量化(Quantization)——用更少的 bit 来存储参数。

graph LR subgraph 量化前 A[FP16: 每参数 2 字节] --> B[70B × 2 = 140GB] end subgraph 量化后 C[INT4: 每参数 0.5 字节] --> D[70B × 0.5 = 35GB] end B --> E[2×A100 80GB] D --> F[1×A100 40GB ✓] style D fill:#4ecdc4 style F fill:#4ecdc4

一、量化基础:用更少的 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 数据类型对比

graph TB subgraph 常见数据类型 FP32["FP32 (32 bit)
范围大,精度高
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
类型位数范围精度用途
FP3232±3.4e38训练(优化器状态)
FP1616±65504训练/推理
BF1616±3.4e38训练(更稳定)
INT88-128~127推理量化
INT44-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.000156

1.4 量化粒度

量化可以在不同粒度上进行:

graph TB subgraph 量化粒度 PT["Per-Tensor
整个张量一个 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 量化方法分类

mindmap root((LLM 量化方法)) 训练后量化 PTQ GPTQ 基于 Hessian 权重量化 AWQ 激活感知 保护重要权重 GGUF/GGML CPU 推理 llama.cpp SmoothQuant 平滑激活 W8A8 量化感知训练 QAT LLM-QAT 训练时量化 效果最好但成本高

2.2 主流方法对比

方法位数精度损失速度适用场景
GPTQ4/8 bitGPU 推理
AWQ4 bit最小GPU 推理
GGUF2-8 bit中等CPU/混合推理
bitsandbytes4/8 bit训练+推理
SmoothQuant8 bit很小最快服务部署

2.3 各方法的技术特点

flowchart TB subgraph GPTQ G1[基于 Hessian 矩阵] G2[逐层量化] G3[误差补偿到后续权重] end subgraph AWQ A1[分析激活值分布] A2[识别重要权重通道] A3[保护重要通道精度] end subgraph GGUF Q1[多种量化级别] Q2[CPU 优化] Q3[混合精度支持] end

三、GPTQ:基于 Hessian 的量化

3.1 核心思想

GPTQ 的核心洞察:量化误差可以通过调整未量化的权重来补偿

基于 Optimal Brain Quantization (OBQ) 算法,使用 Hessian 矩阵来决定量化顺序和误差补偿。

graph LR subgraph GPTQ流程 A[原始权重 W] --> B[选择一列量化] B --> C[计算量化误差] C --> D[补偿到其他列] D --> E{还有未量化的列?} E -->|是| B E -->|否| F[完成] end

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% 的权重通道对模型输出影响巨大

策略:识别这些重要通道,保护它们的精度

graph TB subgraph AWQ流程 A[分析激活值分布] --> B[计算每个通道的重要性] B --> C[对重要通道使用更高精度] C --> D[对不重要通道激进量化] end

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

维度GPTQAWQ
核心思想Hessian 误差补偿激活感知保护
量化速度较慢较快
推理速度更快
精度保持更好
vLLM 支持✅ 更优
推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐

结论:新项目推荐使用 AWQ,与 vLLM 配合效果最佳。


五、GGUF:CPU 推理的王者

5.1 什么是 GGUF?

GGUF(GPT-Generated Unified Format) 是 llama.cpp 使用的模型格式,专为 CPU 推理优化。

前身是 GGML,2023 年升级为 GGUF,支持更多功能。

graph TB subgraph GGUF生态 Model[HuggingFace 模型] --> Convert[转换为 GGUF] Convert --> GGUF[GGUF 文件] GGUF --> LC[llama.cpp] GGUF --> Ollama[Ollama] GGUF --> LMS[LM Studio] GGUF --> Kobold[KoboldCpp] end style GGUF fill:#4ecdc4

5.2 GGUF 量化级别

GGUF 提供多种量化级别,命名规则如 Q4_K_M

量化类型位数大小(7B)质量速度
Q2_K2-3 bit~3 GB最快
Q3_K_S3 bit~3.5 GB较差很快
Q3_K_M3 bit~3.8 GB一般很快
Q4_04 bit~4 GB一般
Q4_K_M4 bit~4.5 GB推荐
Q5_K_M5 bit~5 GB中等
Q6_K6 bit~6 GB很好较慢
Q8_08 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_M

5.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 GB

6.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 速度

graph TB subgraph 量化效果对比 FP16["FP16
精度: ⭐⭐⭐⭐⭐
显存: 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推理速度
FP1613.5 GB5.471x
GPTQ 4-bit4.2 GB5.521.8x
AWQ 4-bit4.2 GB5.502.1x
GGUF Q4_K_M4.5 GB5.581.5x (CPU)
bnb 4-bit4.1 GB5.551.3x

关键发现

  • 4-bit 量化精度损失很小(~1%)
  • AWQ 在速度和精度上都优于 GPTQ
  • GGUF 在 CPU 上性能最好

7.3 如何选择?

flowchart TB A[选择量化方法] --> B{部署环境?} B -->|GPU 服务器| C{框架?} B -->|CPU/边缘设备| D[GGUF + Ollama] B -->|训练| E[bitsandbytes 4-bit] C -->|vLLM| F[AWQ] C -->|TGI| G[GPTQ/AWQ] C -->|transformers| H[GPTQ/bnb] style F fill:#4ecdc4 style D fill:#4ecdc4

简单建议

  • 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 8000

8.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)

九、总结

量化方法速查表

mindmap root((模型量化)) GPU推理 GPTQ Hessian优化 4-bit AWQ 激活感知 推荐首选 CPU推理 GGUF llama.cpp 多种精度 Ollama 最简单 训练 bitsandbytes QLoRA必备 NF4

关键 Takeaway

  1. 量化是必备技能:让大模型在消费级硬件上运行
  2. 4-bit 量化精度损失很小:通常 <1%,完全可用
  3. 选择建议

    • GPU 部署:AWQ + vLLM
    • CPU/本地:GGUF + Ollama
    • 训练:bitsandbytes (QLoRA)
  4. GGUF 命名规则:Q4_K_M 推荐,平衡精度和大小
  5. 显存需求

    • 7B FP16: 14GB → 4-bit: 4GB
    • 70B FP16: 140GB → 4-bit: 35GB

推荐工具

场景工具推荐度
GPU 量化AutoAWQ⭐⭐⭐⭐⭐
GPU 部署vLLM⭐⭐⭐⭐⭐
CPU/本地Ollama⭐⭐⭐⭐⭐
训练bitsandbytes⭐⭐⭐⭐⭐
模型转换llama.cpp⭐⭐⭐⭐

下一步学习

  • [ ] vLLM 推理加速:极致性能
  • [ ] 本地部署实战:Ollama 完全指南
  • [ ] 模型压缩:知识蒸馏

参考资料

  1. GPTQ Paper - GPTQ: Accurate Post-Training Quantization
  2. AWQ Paper - AWQ: Activation-aware Weight Quantization
  3. llama.cpp - GGUF 格式的发源地
  4. bitsandbytes - 8-bit/4-bit 量化库
  5. Ollama - 最简单的本地部署方案

评论区
暂无评论
avatar