趋近智
"好的,我们将理论付诸实践。在前面的部分,我们讨论了评估量化 (quantization)模型的指标和方法。现在,你将亲自动手对一个量化大型语言模型与其原始版本进行基准测试。这项实操练习将有助于你加深对如何衡量量化对性能(速度、内存)和准确性影响的理解。"
量化本质上是关于权衡。我们的目标是减少所需的计算资源(例如内存占用 和推理 (inference)延迟 ),同时尽量减小对模型预测质量的影响,预测质量通常通过准确性或困惑度来衡量。此处量化了这些确切的权衡。
在开始之前,请确保你已安装所需的库。我们将主要使用 Hugging Face 库进行模型加载、生成和评估。你还需要 torch 来处理核心功能,如果加载使用 bitsandbytes 量化 (quantization)的模型,可能还需要 bitsandbytes,或者对于 GPTQ/GGUF 格式的模型则需要 optimum。
pip install transformers torch accelerate datasets evaluate optimum bitsandbytes sentencepiece
# 如果通过optimum中使用ctransformers后端来使用GGUF模型:
pip install optimum[exporters,ctransformers]
# 或者如果通过optimum中使用AutoGPTQ后端:
pip install optimum[exporters,auto-gptq]
对于本练习,你将需要一个LLM的两个版本:
bitsandbytes 加载)甚至QAT获得的低精度版本(例如 INT8、INT4)。你可以使用在先前练习中量化的模型,或者加载 Hugging Face Hub 上可用的预量化模型。为了保持一致性和加快执行速度,可以考虑使用像 gpt2、distilgpt2 这样的小型模型,或者如果原始和量化版本都可用,则使用 Mistral/Llama 的小型变体。确保你有一块支持 CUDA 的 GPU,以便进行有效的性能测量。
让我们定义模型标识符。请将它们替换为你所选模型的实际 Hugging Face Hub ID 或本地路径。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import time
import numpy as np
# --- 配置 ---
base_model_id = "gpt2" # 示例:替换为你的基础模型(例如:"meta-llama/Llama-2-7b-hf")
quantized_model_id = "gpt2" # 示例:替换为你的量化模型ID或路径
# 如果使用bitsandbytes加载4位模型,ID可能相同,但加载方式不同
load_in_4bit = True # 如果加载时使用bitsandbytes 4位量化,则设为True
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")
# --- 加载分词器(通常两者相同)---
tokenizer = AutoTokenizer.from_pretrained(base_model_id)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token # 如果缺失,设置填充 token
# --- 加载基础模型 ---
print(f"Loading base model: {base_model_id}")
# 如果使用GPU且模型支持,则加载半精度
base_model = AutoModelForCausalLM.from_pretrained(
base_model_id,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
device_map=device # 简单加载到目标设备
)
base_model.eval() # 设置为评估模式
print("Base model loaded.")
# --- 加载量化模型 ---
print(f"Loading quantized model: {quantized_model_id}")
if load_in_4bit and device == "cuda":
from transformers import BitsAndBytesConfig
# 示例:使用bitsandbytes 4位量化加载
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16 # 可选:计算数据类型
)
quantized_model = AutoModelForCausalLM.from_pretrained(
quantized_model_id,
quantization_config=bnb_config,
device_map=device # bitsandbytes 处理设备放置
)
print("Quantized model loaded using bitsandbytes 4-bit config.")
# 如果需要,在此处添加其他加载方法(例如,使用Optimum加载GPTQ/GGUF)
# elif quantized_model_id.endswith(".gguf"): # 示例:使用Optimum和CTransformers
# from optimum.quanto import CtransformersModelForCausalLM
# quantized_model = CtransformersModelForCausalLM.from_pretrained(quantized_model_id, device_map=device)
# print("Quantized GGUF model loaded using Optimum.")
else:
# 假设加载的是标准的Hugging Face模型(例如,如果使用了QAT或者以其他方式预量化)
# 根据特定的量化格式,必要时调整数据类型和加载方式
quantized_model = AutoModelForCausalLM.from_pretrained(
quantized_model_id,
torch_dtype=torch.float16 if device == "cuda" else torch.float32, # 可能需要调整
device_map=device
)
print("Quantized model loaded (adjust loading logic if not using bitsandbytes 4-bit).")
quantized_model.eval() # 设置为评估模式
请务必根据
quantized_model的量化和保存方式调整其加载逻辑。此示例展示了通过bitsandbytes加载。如果你有 GPTQ 或 GGUF 模型,通常会使用optimum等库以及相应的后端(auto-gptq或ctransformers)。
现在,让我们衡量速度和内存使用情况。我们将关注延迟(每次生成所需的时间)和峰值内存消耗。
延迟是处理单个输入所需的时间。我们可以通过对 generate 函数计时来测量它。为了获得准确的GPU计时,在启动和停止计时器之前进行同步很重要。
# --- 延迟基准测试 ---
prompt = "The future of AI is"
inputs = tokenizer(prompt, return_tensors="pt").to(device)
# 热身运行(对CUDA很重要)
_ = base_model.generate(**inputs, max_new_tokens=50)
_ = quantized_model.generate(**inputs, max_new_tokens=50)
if device == "cuda":
torch.cuda.synchronize()
# 测量基础模型延迟
start_time = time.perf_counter()
_ = base_model.generate(**inputs, max_new_tokens=50, pad_token_id=tokenizer.pad_token_id)
if device == "cuda":
torch.cuda.synchronize()
end_time = time.perf_counter()
base_latency = end_time - start_time
print(f"Base Model Latency: {base_latency:.4f} seconds")
# 测量量化模型延迟
start_time = time.perf_counter()
_ = quantized_model.generate(**inputs, max_new_tokens=50, pad_token_id=tokenizer.pad_token_id)
if device == "cuda":
torch.cuda.synchronize()
end_time = time.perf_counter()
quantized_latency = end_time - start_time
print(f"Quantized Model Latency: {quantized_latency:.4f} seconds")
if base_latency > 0:
speedup = base_latency / quantized_latency
print(f"Speedup: {speedup:.2f}x")
else:
print("Base latency was zero, cannot calculate speedup.")
多次运行此代码或对多个提示取平均值,以获得更稳定的结果。你应该会看到量化 (quantization)模型具有更低的延迟,尤其是在原生支持低精度操作的硬件上。
我们可以使用 PyTorch 的内存管理函数来估计推理 (inference)期间的 GPU 峰值内存使用量。
# --- 内存基准测试 ---
def get_peak_memory_mb(model, inputs):
"""测量模型生成过程中的GPU峰值内存使用量。"""
if device != "cuda":
print("Memory measurement only available for CUDA devices.")
return 0
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
try:
_ = model.generate(**inputs, max_new_tokens=50, pad_token_id=tokenizer.pad_token_id)
torch.cuda.synchronize()
peak_memory = torch.cuda.max_memory_allocated() / (1024**2) # 将字节转换为MB
except Exception as e:
print(f"Error during memory measurement: {e}")
peak_memory = 0
torch.cuda.empty_cache()
return peak_memory
# 测量基础模型内存
base_memory_mb = get_peak_memory_mb(base_model, inputs)
print(f"Base Model Peak Memory: {base_memory_mb:.2f} MB")
# 测量量化模型内存
quantized_memory_mb = get_peak_memory_mb(quantized_model, inputs)
print(f"Quantized Model Peak Memory: {quantized_memory_mb:.2f} MB")
if base_memory_mb > 0:
memory_reduction = base_memory_mb / quantized_memory_mb
print(f"Memory Reduction Factor: {memory_reduction:.2f}x")
print(f"Memory Savings: {(1 - (quantized_memory_mb / base_memory_mb)) * 100:.2f}%")
else:
print("Base memory usage was zero or could not be measured.")
量化模型应显示显著更低的峰值内存使用量,这与其权重 (weight)(以及可能根据方法,激活值)的精度降低直接相关。
性能提升只有在模型对其预期任务保持足够准确时才有价值。我们需要使用合适的指标评估量化 (quantization)模型的质量。
困惑度是语言模型常用的内在指标。它衡量模型预测文本序列的能力。较低的困惑度通常表示更好的模型。我们可以使用 evaluate 库和像 WikiText 这样的标准数据集。
import evaluate
from datasets import load_dataset
from tqdm import tqdm
# --- 困惑度评估 ---
try:
perplexity_metric = evaluate.load("perplexity", module_type="metric")
# 在此示例中使用一小部分数据以加快评估速度
dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="test[:1%]")
# 或者使用完整测试集:split="test"
# 预处理文本数据(拼接和分块)以计算困惑度
# 此方法将整个数据集文本作为单个序列处理
encodings = tokenizer("\n\n".join(dataset["text"]), return_tensors="pt")
max_length = base_model.config.n_positions # 使用模型的最大序列长度
stride = 512 # 块之间的重叠量
seq_len = encodings.input_ids.size(1)
nlls_base = []
nlls_quantized = []
prev_end_loc = 0
print("Calculating perplexity...")
for begin_loc in tqdm(range(0, seq_len, stride)):
end_loc = min(begin_loc + max_length, seq_len)
trg_len = end_loc - prev_end_loc # 在最后一次循环中可能与步长不同
input_ids = encodings.input_ids[:, begin_loc:end_loc].to(device)
target_ids = input_ids.clone()
target_ids[:, :-trg_len] = -100 # 忽略重叠 token 的损失计算
with torch.no_grad():
# 基础模型
outputs_base = base_model(input_ids, labels=target_ids)
neg_log_likelihood_base = outputs_base.loss * trg_len # 按目标长度缩放损失
nlls_base.append(neg_log_likelihood_base)
# 量化模型
outputs_quantized = quantized_model(input_ids, labels=target_ids)
neg_log_likelihood_quantized = outputs_quantized.loss * trg_len # 按目标长度缩放损失
nlls_quantized.append(neg_log_likelihood_quantized)
prev_end_loc = end_loc
if end_loc == seq_len:
break
# 计算困惑度
ppl_base = torch.exp(torch.stack(nlls_base).sum() / end_loc)
ppl_quantized = torch.exp(torch.stack(nlls_quantized).sum() / end_loc)
print(f"\nBase Model Perplexity: {ppl_base.item():.4f}")
print(f"Quantized Model Perplexity: {ppl_quantized.item():.4f}")
print(f"Perplexity Increase: {ppl_quantized.item() - ppl_base.item():.4f}")
except ImportError:
print("Please install 'evaluate' and 'datasets' to run perplexity benchmark.")
except Exception as e:
print(f"Could not run perplexity evaluation: {e}")
这种困惑度计算采用了因果语言模型常用的滑动窗口方法。请注意,在大型数据集上计算困惑度可能非常耗时。使用较小的子集(
split="test[:1%]")可以提供快速估计。
另外,或者作为补充,你可以在模型可能被用于的特定任务上对其进行评估,例如问答、摘要或情感分析。这通常能更直接地衡量实际用途。像 lm-evaluation-harness 这样的框架就是为此目的而设计的,它提供了许多任务的标准评估设置。设置 lm-evaluation-harness 更复杂,超出了本次基本实操的范围,但它是严格LLM评估的标准。
对于本笔记本中更简单的基于任务的评估,你可以调整生成循环,使其在一个特定任务数据集(例如,用于是/否问题的 BoolQ)上运行并计算准确性。
现在,整理你的发现。一个简单的表格通常很有效:
| 指标 | 基础模型 | 量化 (quantization)模型 | 变化 |
|---|---|---|---|
| 延迟(秒) | base_latency |
quant_latency |
speedup倍快 |
| 峰值内存(MB) | base_memory |
quant_memory |
reduction倍小 |
| 困惑度 | ppl_base |
ppl_quantized |
increase更高 |
| 任务准确率(%) | (如果已测量) | (如果已测量) | (差异) |
(用你测量的结果替换占位符值)
可视化权衡结果。一个简单的散点图,将速度提升/内存减少与准确性下降(例如,困惑度百分比增加)进行比较,可以提供好的参考。
性能指标(如延迟和内存减少)对比评估指标(如困惑度增加)。示例展示了一个4位量化模型获得了1.85倍的速度提升,同时困惑度增加了5.2%。请用你自己的测量值替换这些数据。
解释结果:
这种亲自动手的基准测试过程提供了具体数据,以指导有关部署量化模型的决策,确保你在效率提升与所需的预测性能水平之间取得平衡。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•