课程0基础Agent开发课 / MLOps与模型部署 / 模型推理优化-量化剪枝与加速
— 11 min read

模型推理优化-量化剪枝与加速

训练好的模型如何在生产环境高效运行?

模型推理优化:量化、剪枝与加速

训练好的模型如何在生产环境高效运行?

先说清楚一个基本矛盾:训练时追求精度,推理时追求速度和成本。一个 70B 参数的 LLM,以 float16 格式存储需要约 140GB 显存,大多数公司的硬件根本装不下,更别说服务多个并发用户了。

推理优化的目标就是:在可接受的精度损失下,大幅降低硬件要求和推理延迟,让模型能真正用起来。


1.1 推理优化的四个目标

模型优化技术对比图
量化/剪枝/Flash Attention/ONNX 四种优化技术的原理、压缩比与精度损失对比

目标 意义 常用方法
降低延迟 用户等待时间更短,体验更好 Flash Attention、KV Cache
提高吞吐量 同时服务更多用户 vLLM PagedAttention、连续批处理
减少显存占用 在更小的 GPU 上运行更大的模型 量化(INT8/INT4)、剪枝
减小模型体积 便于部署到边缘设备 量化、ONNX 转换

1.2 量化:减少数值精度,是最实用的优化

量化是目前收益最高、副作用最小的优化方法,应该是优化的第一选择。

核心思想:神经网络的权重通常以 float32(每个数字占 4 字节)或 float16(2 字节)存储。量化把这些高精度数值压缩成低精度格式——int8 每个数字只占 1 字节,int4 只占半字节,从而减少存储空间和计算量。

直觉类比:把高分辨率照片压缩成低分辨率——文件小很多,但看起来几乎没有差别。关键问题是:压多少精度会开始明显影响效果?实践表明,从 float16 降到 int8,效果损失通常不到 1%;降到 int4,损失在 1-3%,对大多数应用可以接受。

python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

model_name = "Qwen/Qwen2.5-7B-Instruct"

# 方法一:INT8 量化(推荐用于生产环境)
# 显存减少约 50%,推理速度基本持平,精度损失 < 1%
model_int8 = AutoModelForCausalLM.from_pretrained(
    model_name,
    load_in_8bit=True,   # 启用 INT8 量化
    device_map="auto"    # 自动分配 GPU/CPU
)

# 方法二:4-bit NF4 量化(推荐用于显存受限场景)
# 显存减少约 75%,精度损失 1-3%,是 QLoRA 微调时使用的格式
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",               # NF4 格式,精度优于普通 int4
    bnb_4bit_compute_dtype=torch.float16     # 计算时恢复为 float16,提升精度
)
model_4bit = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto"
)

# 方法三:GGUF 量化(用于 llama.cpp / Ollama,适合本地部署)
# 优势:支持纯 CPU 推理,适合没有 GPU 的场景
# 使用 llama.cpp 工具转换:
# python convert_hf_to_gguf.py model_dir --outtype q4_k_m --outfile model_q4.gguf

量化方案对比

量化方式 显存节省 速度变化 精度损失 适用场景
FP16(基线) 0% 基线 0% 训练、高精度推理
INT8 ~50% 接近持平 < 1% 生产环境首选
INT4 (NF4) ~75% 轻微下降 1-3% 显存受限场景
GGUF Q4_K_M ~75% CPU 可运行 1-3% 本地/边缘部署

怎么选:优先试 INT8,精度够用且显存节省一半。如果 INT8 后显存还不够,换 INT4 NF4。本地部署用 GGUF + Ollama。


1.3 Flash Attention:长上下文的必需品

Flash Attention 不改变计算结果,只改变计算方式。它重写了注意力的计算顺序,大幅减少显存访问次数,从而在长上下文场景下显著提速。

适用条件很关键

Flash Attention 的收益对序列长度高度敏感:

  • 序列 < 512 token:收益有限,有时因额外开销反而略慢
  • 序列 1K-2K token:开始有明显收益,速度提升约 1.5-2x
  • 序列 2K+ token:收益显著,速度提升 2-4x,显存节省约 30%
  • 序列 32K+ token:不用 Flash Attention 基本跑不动

原因是标准注意力的显存复杂度是 O(n²),序列越长,Flash Attention 的优势越大。短上下文(简单问答)用量化,长上下文(文档分析、多轮对话)用 Flash Attention。

python
from transformers import AutoModelForCausalLM
import torch

# 安装:pip install flash-attn --no-build-isolation
# 要求:NVIDIA GPU Ampere 架构及以上(A100/A10/RTX 30xx+)

# 适合场景:处理长文档、长对话历史(2K+ token)
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct",
    torch_dtype=torch.float16,
    attn_implementation="flash_attention_2",  # 启用 Flash Attention 2
    device_map="auto"
)

# 验证是否启用成功
print(model.config._attn_implementation)  # 应输出 "flash_attention_2"

1.4 剪枝:删除冗余神经元

剪枝的思路是:神经网络中有大量权重接近零的连接,这些连接贡献极小,可以删除。

非结构化剪枝 vs 结构化剪枝

  • 非结构化剪枝:随机将单个权重置零。存储空间减少,但不改变矩阵形状,GPU 不能加速。
  • 结构化剪枝:删除整个神经元、注意力头或卷积滤波器。改变了网络结构,GPU 计算量真正减少。

对于部署提速,结构化剪枝更有实用价值。

python
import torch
import torch.nn as nn
import torch.nn.utils.prune as prune

class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(128, 64)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(64, 10)

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

model = SimpleNet()

# 结构化剪枝:按 L1 范数删除整行(对应整个输出神经元)
# amount=0.3 表示删除 30% 的输出神经元
prune.ln_structured(
    model.fc1,
    name="weight",
    amount=0.3,
    n=1,        # L1 范数
    dim=0       # 按行(输出维度)剪枝
)

# 查看剪枝效果
weight = model.fc1.weight
sparsity = (weight == 0).float().mean().item()
print(f"fc1 层权重稀疏度: {sparsity:.1%}")

# 永久化剪枝(实际删除参数)
prune.remove(model.fc1, "weight")

total_params = sum(p.numel() for p in model.parameters())
nonzero_params = sum(p.nonzero().shape[0] for p in model.parameters())
print(f"总参数量: {total_params}")
print(f"非零参数量: {nonzero_params}")
print(f"整体稀疏度: {1 - nonzero_params/total_params:.1%}")

剪枝的注意事项

  1. 剪枝后通常需要微调来恢复精度损失,直接剪枝会导致较大性能下降
  2. 对 LLM,更常用的是注意力头剪枝(删除贡献最小的注意力头)
  3. 剪枝 + 量化可以组合,获得更高压缩率

1.5 ONNX:跨框架部署,CPU 推理加速

ONNX(Open Neural Network Exchange)是模型格式标准,允许把 PyTorch 训练的模型导出为通用格式,然后在 CPU 上用 ONNX Runtime 高效运行。

适合场景:没有 GPU 的服务器、需要在 CPU 上部署分类/NLU 类型的小模型(BERT 系列)。

python
import torch
import torch.onnx
from transformers import AutoModelForSequenceClassification, AutoTokenizer

model_name = "bert-base-chinese"
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
model.eval()

# 准备示例输入
sample_text = "今天天气很好"
inputs = tokenizer(sample_text, return_tensors="pt", max_length=128, padding="max_length")

# 导出为 ONNX 格式
torch.onnx.export(
    model,
    (inputs['input_ids'], inputs['attention_mask']),
    "bert_classifier.onnx",
    input_names=['input_ids', 'attention_mask'],
    output_names=['logits'],
    dynamic_axes={                           # 允许动态 batch size 和序列长度
        'input_ids': {0: 'batch_size', 1: 'sequence'},
        'attention_mask': {0: 'batch_size', 1: 'sequence'},
        'logits': {0: 'batch_size'}
    },
    opset_version=14
)
print("ONNX 模型已导出到 bert_classifier.onnx")

# 用 ONNX Runtime 进行推理(pip install onnxruntime)
import onnxruntime as ort
import numpy as np

session = ort.InferenceSession(
    "bert_classifier.onnx",
    providers=['CPUExecutionProvider']  # CPU 推理
)

inputs_onnx = {
    'input_ids': inputs['input_ids'].numpy(),
    'attention_mask': inputs['attention_mask'].numpy()
}
outputs = session.run(None, inputs_onnx)
print(f"ONNX 推理结果维度: {outputs[0].shape}")

ONNX Runtime 在 CPU 上比直接用 PyTorch 推理快 1.5-3 倍,适合无 GPU 的生产服务器部署 BERT 类小模型。


1.6 推理性能对比

优化方法 显存节省 速度提升 精度损失 适用场景
INT8 量化 ~50% 接近持平 < 1% 生产环境,平衡首选
INT4 量化 ~75% 接近持平 1-3% 显存极度受限
结构化剪枝 10-40% 10-40% 需微调恢复 边缘设备部署
Flash Attention ~30% 2-4x(2K+ token) 0% 长上下文场景
ONNX Runtime 0% 1.5-3x(CPU) 0% 无 GPU 的 CPU 部署
vLLM 0% 3-10x(高并发) 0% 生产级 LLM 服务

1.7 怎么选优化方案

根据实际场景选,不要过度优化:

本地单机推理(消费级 GPU 或无 GPU)
→ GGUF 量化 + Ollama,4-bit 量化可在 16GB 内存的 MacBook 上运行 7B 模型

生产 API 服务(有 GPU,中等并发,长上下文)
→ INT8 量化 + Flash Attention(序列 > 2K 时)

生产高并发 LLM 服务
→ vLLM + INT4 量化(详见第 04 篇)

边缘设备 / IoT 部署
→ 结构化剪枝 + INT4 量化 + ONNX 导出,最大化压缩

CPU 服务器,无 GPU
→ ONNX Runtime(BERT 类小模型)或 GGUF(生成式模型)

优化优先级:量化 > Flash Attention(长序列)> vLLM(高并发)> 剪枝(边缘部署)

多数情况只需要量化就够了,不要过度优化。

本页目录