量化以模型精度换取更小的内存占用,从而允许大型模型在更广泛的设备上运行。
文章目录
支持的硬件
下表显示了 vLLM 中各种量化实现与不同硬件平台的兼容性:
- Volta 指的是 SM 7.0,Turing 指的是 SM 7.5,Ampere 指的是 SM 8.0/8.6,Ada 指的是 SM 8.9,Hopper 指的是 SM 9.0。
- ✅︎ 表示指定硬件支持量化方法。
随着 vLLM 不断发展和扩大对不同硬件平台和量化方法的支持,本兼容性图表可能会发生变化。
有关硬件支持和量化方法的最新信息,请参阅 vllm/model_executor/layers/quantization 或咨询 vLLM 开发团队。
AutoAWQ
要创建新的 4 位量化模型,可以利用 AutoAWQ。量化会将模型的精度从 FP16 降低到 INT4,从而有效地将文件大小减少约 70%。这样做的主要好处是降低了延迟和内存使用率。
一个FP16类型数据存储占用两个字节,INT4只需半个字节,差了四倍,大约减少了约70%。
您可以安装 AutoAWQ 或从 Huggingface 上的 400 多个模型中选择一个,对自己的模型进行量化。
pip install autoawq
安装 AutoAWQ 后,您就可以量化模型了。下面是一个如何量化 mistralai/Mistral-7B-Instruct-v0.2 的示例:
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = 'mistralai/Mistral-7B-Instruct-v0.2'
quant_path = 'mistral-instruct-v0.2-awq'
quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM" }
# Load model
model = AutoAWQForCausalLM.from_pretrained(
model_path, **{"low_cpu_mem_usage": True, "use_cache": False}
)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# Quantize
model.quantize(tokenizer, quant_config=quant_config)
# Save quantized model
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
print(f'Model is quantized and saved at "{quant_path}"')
要使用 vLLM 运行 AWQ 模型,可以使用 TheBloke/Llama-2-7b-Chat-AWQ 和以下命令:
python examples/offline_inference/llm_engine_example.py --model TheBloke/Llama-2-7b-Chat-AWQ --quantization awq
通过 LLM
类还可直接支持 AWQ 模型:
from vllm import LLM, SamplingParams
# Sample prompts.
prompts = [
"Hello, my name is",
"The president of the United States is",
"The capital of France is",
"The future of AI is",
]
# Create a sampling params object.
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
# Create an LLM.
llm = LLM(model="TheBloke/Llama-2-7b-Chat-AWQ", quantization="AWQ")
# Generate texts from the prompts. The output is a list of RequestOutput objects
# that contain the prompt, generated text, and other information.
outputs = llm.generate(prompts, sampling_params)
# Print the outputs.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
过程解释
FP16 和 INT4 的概念
-
FP16(Half Precision Floating Point):这是一种浮点数表示法,使用16位(2字节)来存储数值。它包括1位符号位、5位指数位和10位尾数位。这种格式允许表示范围广泛但精度相对较低的数值,非常适合于深度学习中的模型权重和激活值表示,在减少计算资源需求的同时保持较高的性能。
-
INT4(Integer 4-bit):这是一种整数表示法,仅使用4位(半字节)来存储数值。由于其极低的比特数,INT4只能表示非常有限范围内的整数值,并且没有小数部分。这使得它在数值表达上极为受限,但非常适合用于对精度要求不高、更注重效率的应用场景,比如某些特定类型的神经网络推理任务。
从 FP16 降低到 INT4”的含义
将数值精度从FP16降低到INT4意味着将原本以半精度浮点数形式存储的数据转换为更低精度的4位整数形式。
实例说明
假设我们有一个简单的神经网络层,其中包含一些权重值。这些权重最初是以FP16格式存储的。例如,考虑一个权重值 w = -0.375
:
-
原始FP16表示:首先,这个值
-0.375
被表示为FP16格式的一个数。虽然具体的二进制表示可能复杂,但从概念上讲,它是用16位来精确表示这个浮点数的。 -
量化至INT4:为了将其量化到INT4,我们需要定义一个缩放因子和零点(offset),以便将浮点数映射到整数范围内。假设我们的目标是使用8个不同的整数值(因为INT4可以表示16个不同的值,但我们可能只使用正负各一半)。如果选择缩放因子为
0.25
,那么-0.375
将被量化为最接近的可表示值,即-1
(因为-0.375 / 0.25 ≈ -1.5
,四舍五入后得到-1
)。这样,原本的FP16值就被近似为一个INT4值。 -
结果对比:在这个例子中,原来的
-0.375
变成了-1
,这意味着信息丢失了(特别是小数部分),但是存储空间和计算复杂度都显著降低了。
通过这样的过程,我们可以看到,尽管量化导致了一定程度的精度损失,但它极大地提高了计算效率和减少了存储需求,这对于部署在资源受限设备上的机器学习模型尤为重要。
BitsAndBytes
vLLM 现在支持 BitsAndBytes,可实现更高效的模型推理。BitsAndBytes 对模型进行量化,以减少内存使用量并提高性能,而不会明显牺牲准确性。与其他量化方法相比,BitsAndBytes 无需使用输入数据校准量化模型。
以下是使用 BitsAndBytes 和 vLLM 的步骤。
pip install bitsandbytes>=0.45.0
VLLM读取模型的配置文件,并支持机上in-flight量化和预量化pre-quantized检查点。
您可以在 https://huggingface.co/models?other=bitsandbytes 上找到 bitsandbytes 量化模型。通常,这些资源库都有一个 config.json 文件,其中包括一个 quantization_config 部分。
vllm读取4位量化模型
from vllm import LLM
import torch
# unsloth/tinyllama-bnb-4bit is a pre-quantized checkpoint.
model_id = "unsloth/tinyllama-bnb-4bit"
llm = LLM(model=model_id, dtype=torch.bfloat16, trust_remote_code=True, \
quantization="bitsandbytes", load_format="bitsandbytes")
in-flight量化:加载为 4 位量化模型
from vllm import LLM
import torch
model_id = "huggyllama/llama-7b"
llm = LLM(model=model_id, dtype=torch.bfloat16, trust_remote_code=True, \
quantization="bitsandbytes", load_format="bitsandbytes")
OpenAI兼容的服务加载量化模型
将以下内容添加到您的 4 位模型参数中:
--quantization bitsandbytes --load-format bitsandbytes
INT4 W4A16
vLLM 支持将权重量化为 INT4,以节省内存并加速推理。这种量化方法特别适用于减少模型大小,并在每秒查询次数(QPS)较少的工作负载中保持低延迟。
请访问随时可以与VLLM一起使用的主流LLM的量化INT4模型的HF集合。
计算能力大于 8.0(Ampere、Ada Lovelace、Hopper、Blackwell)的英伟达™(NVIDIA®)图形处理器支持 INT4 计算。
安装依赖库
要在 vLLM 中使用 INT4 量化,需要安装 llm-compressor 库:
pip install llmcompressor
如何量化
量化过程包括四个主要步骤:
- 加载模型
- 准备校准数据
- 执行量化
- 评估 vLLM 的准确性
- 加载模型
使用transformers
的AutoModel
类加载模型和tokenizer
:
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, device_map="auto", torch_dtype="auto",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
- 准备校准数据
将权重量化到 INT4 时,需要样本数据来估计权重更新和校准刻度。最好使用与部署数据非常匹配的校准数据。对于通用的指令调整模型,可以使用ultrachat
这样的数据集:
from datasets import load_dataset
NUM_CALIBRATION_SAMPLES = 512
MAX_SEQUENCE_LENGTH = 2048
# Load and preprocess the dataset
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft")
ds = ds.shuffle(seed=42).select(range(NUM_CALIBRATION_SAMPLES))
def preprocess(example):
return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False)}
ds = ds.map(preprocess)
def tokenize(sample):
return tokenizer(sample["text"], padding=False, max_length=MAX_SEQUENCE_LENGTH, truncation=True, add_special_tokens=False)
ds = ds.map(tokenize, remove_columns=ds.column_names)
- 执行量化
现在,应用量化算法:
from llmcompressor.transformers import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor.modifiers.smoothquant import SmoothQuantModifier
# Configure the quantization algorithms
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
# Apply quantization
oneshot(
model=model,
dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# Save the compressed model
SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A16-G128"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
这个过程创建了一个 W4A16 模型,权重量化为 4 位整数。
- 评估准确性
量化后,您可以在 vLLM 中加载并运行模型:
from vllm import LLM
model = LLM("./Meta-Llama-3-8B-Instruct-W4A16-G128")
要评估准确性,可以使用 lm_eval
:
lm_eval --model vllm \
--model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A16-G128",add_bos_token=true \
--tasks gsm8k \
--num_fewshot 5 \
--limit 250 \
--batch_size 'auto'
注意:量化模型可能对 bos 标记的存在很敏感。请确保在运行评估时加入 add_bos_token=True 参数。
最佳实践
- 校准数据从 512 个样本开始,如果精度下降,则增加样本数量
- 确保校准数据包含多种样本,以防止对特定用例的过度拟合
- 将序列长度设置为 2048 作为起点
- 使用模型训练时使用的聊天模板或指令模板
- 如果您已经对模型进行了微调,请考虑使用训练数据的样本进行校准
- 调整量化算法的关键超参数:
dampening_frac
设置 GPTQ 算法的影响程度。较低的值可以提高准确度,但会导致数值不稳定,从而导致算法失败。actorder
设置激活顺序。压缩层权重时,通道量化的顺序很重要。设置 actorder=“weight” 可以在不增加延迟的情况下提高准确度。
下面是一个扩展量化配方的示例,你可以根据自己的使用情况进行调整:
from compressed_tensors.quantization import (
QuantizationArgs,
QuantizationScheme,
QuantizationStrategy,
QuantizationType,
)
recipe = GPTQModifier(
targets="Linear",
config_groups={
"config_group": QuantizationScheme(
targets=["Linear"],
weights=QuantizationArgs(
num_bits=4,
type=QuantizationType.INT,
strategy=QuantizationStrategy.GROUP,
group_size=128,
symmetric=True,
dynamic=False,
actorder="weight",
),
),
},
ignore=["lm_head"],
update_size=NUM_CALIBRATION_SAMPLES,
dampening_frac=0.01
)
常见问题与支持
如果您遇到任何问题或有任何功能请求,请在 vllm-project/llm-compressor
GitHub 代码库中提交问题。llm-compressor
中完整的 INT4 量化示例可在此处获取。
INT8量化 W8A8
vLLM 支持将权重和激活量量化为 INT8,以节省内存和加速推理。这种量化方法尤其适用于在保持良好性能的同时缩小模型大小。
请访问可与 vLLM 配合使用的常用 LLM 的量化 INT8 模型的常见集合。
注意:计算能力大于 7.5(图灵、安培、Ada Lovelace、Hopper、Blackwell)的英伟达™(NVIDIA®)图形处理器支持 INT8 计算。
使用vLLM如何实现INT8量化,请参考教程
FP8量化 W8A8
vLLM 使用 Nvidia H100 和 AMD MI300x 等 GPU 上的硬件加速支持 FP8(8 位浮点)权重和激活量化。目前,只有 Hopper 和 Ada Lovelace GPU 正式支持 W8A8。
Ampere GPU 支持使用 Marlin 内核的 W8A16(仅重量 FP8)。利用 FP8 对模型进行量化,可将模型内存要求降低 2 倍,吞吐量最多可提高 1.6 倍,而对精度的影响却很小。
请访问可与 vLLM 配合使用的常用 LLM 的量化 FP8 量化模型的常见集合。
硬件通常支持的 FP8 类型有两种不同的表示方法,每种方法在不同情况下都有用:
- E4M3: 由 1 个符号位、4 个指数位和 3 个尾数位组成。它可以存储高达
+/-448
和nan
的数值。 - E5M2: 由 1 个符号位、5 个指数位和 2 个尾数位组成。它可以存储高达
+/-57344
、+/- inf
和nan
的数值。动态范围增加的代价是存储值的精度降低。
注意:计算能力大于 8.9(Ada Lovelace,Hopper)的英伟达™(NVIDIA®)图形处理器支持 FP8 计算。FP8 模型将在计算能力大于 8.0(安培)的 W8A16 上运行,使用 FP8 Marlin。
在线动态量化快速入门
vLLM 可以将原始精度 BF16/FP16 模型动态量化为 FP8,而无需任何校准数据。您可以通过在命令行中指定–quantization=“fp8 ”或在 LLM 构造函数中设置 quantization=“fp8” 来启用该功能。
在这种模式下,所有线性模块(除了最后的 lm_head)的权重都量化为 FP8_E4M3 精度,并按张量标度。在每次前向传递过程中,都会计算激活的最小值和最大值,以提供高精度的动态单位张量标度。因此,在这种模式下,延迟的改善是有限的。
from vllm import LLM
model = LLM("facebook/opt-125m", quantization="fp8")
# INFO 06-10 17:55:42 model_runner.py:157] Loading model weights took 0.1550 GB
result = model.generate("Hello, my name is")
警告:目前,我们以原始精度加载模型,然后再将其量化为 8 位,因此需要足够的内存来加载整个模型。
利用静态激活缩放因子进行离线量化
通过启用 activation_scheme=“static”
(静态)参数,可以使用 AutoFP8 和校准数据为权重和激活生成每个张量的静态刻度。
from datasets import load_dataset
from transformers import AutoTokenizer
from auto_fp8 import AutoFP8ForCausalLM, BaseQuantizeConfig
pretrained_model_dir = "meta-llama/Meta-Llama-3-8B-Instruct"
quantized_model_dir = "Meta-Llama-3-8B-Instruct-FP8"
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_dir, use_fast=True)
tokenizer.pad_token = tokenizer.eos_token
# Load and tokenize 512 dataset samples for calibration of activation scales
ds = load_dataset("mgoin/ultrachat_2k", split="train_sft").select(range(512))
examples = [tokenizer.apply_chat_template(batch["messages"], tokenize=False) for batch in ds]
examples = tokenizer(examples, padding=True, truncation=True, return_tensors="pt").to("cuda")
# Define quantization config with static activation scales
quantize_config = BaseQuantizeConfig(quant_method="fp8", activation_scheme="static")
# Load the model, quantize, and save checkpoint
model = AutoFP8ForCausalLM.from_pretrained(pretrained_model_dir, quantize_config)
model.quantize(examples)
model.save_quantized(quantized_model_dir)
最后,您可以直接在 vLLM 中加载量化后的模型检查点。
from vllm import LLM
model = LLM(model="Meta-Llama-3-8B-Instruct-FP8/")
# INFO 06-10 21:15:41 model_runner.py:159] Loading model weights took 8.4596 GB
result = model.generate("Hello, my name is")
使用vLLM如何实现INT8量化,请参考教程