优化
🤗 Optimum Intel 提供了一个 openvino
包,它使您能够使用 NNCF 框架,对 🤗 中心托管的许多模型应用各种模型压缩方法,例如量化、剪枝等。
训练后
量化是一种通过使用较低精度的数据类型(如 8 位或 4 位)来表示权重和/或激活来降低运行推理的计算和内存成本的技术。
仅权重量化
量化可以应用于模型的线性、卷积和嵌入层,从而能够在内存有限的设备上加载大型模型。例如,在应用 8 位量化时,生成的模型将比其 fp32 对应模型小 4 倍。对于 4 位量化,内存减少量在理论上可以达到 8 倍,但在实践中更接近 6 倍。
8 位
对于 8 位权重量化,您可以设置 load_in_8bit=True
以将模型的权重加载为 8 位
from optimum.intel import OVModelForCausalLM
model_id = "helenai/gpt2-ov"
model = OVModelForCausalLM.from_pretrained(model_id, load_in_8bit=True)
# Saves the int8 model that will be x4 smaller than its fp32 counterpart
model.save_pretrained(saving_directory)
如果未指定,则当模型参数超过 10 亿个时,默认情况下 load_in_8bit
将被设置为 True
,以将模型导出到 OpenVINO 格式(使用 export=True
)。您可以使用 load_in_8bit=False
禁用它。
您也可以提供 quantization_config
来指定其他优化参数。
4 位
对于 4 位权重量化,您需要一个 quantization_config
来定义优化参数,例如
from optimum.intel import OVModelForCausalLM, OVWeightQuantizationConfig
quantization_config = OVWeightQuantizationConfig(bits=4)
model = OVModelForCausalLM.from_pretrained(model_id, quantization_config=quantization_config)
您可以调整量化参数以实现更好的性能精度权衡,如下所示
quantization_config = OVWeightQuantizationConfig(bits=4, sym=False, ratio=0.8, dataset="wikitext2")
默认情况下,量化方案将是 非对称的,要使其成为 对称的,您可以添加 sym=True
。
对于 4 位量化,您也可以在量化配置中指定以下参数
group_size
参数将定义用于量化的组大小,-1
将导致逐列量化。ratio
参数控制 4 位和 8 位量化之间的比率。如果设置为 0.9,则表示 90% 的层将被量化为int4
,而 10% 将被量化为int8
。
较小的 group_size
和 ratio
值通常会提高精度,但会牺牲模型大小和推理延迟。
通过采用以下数据相关方法之一,可以进一步提高 4 位权重压缩模型的质量
- AWQ 代表激活感知量化,是一种算法,用于调整模型权重以实现更准确的 4 位压缩。它会稍微提高压缩 LLM 的生成质量,但需要在校准数据集上花费大量额外时间和内存来调整权重。请注意,模型中可能没有匹配的模式来应用 AWQ,在这种情况下,它将被跳过。
- 尺度估计是一种方法,用于调整量化尺度以最小化原始层和压缩层之间的
L2
误差。需要提供数据集才能运行尺度估计。使用此方法也会产生额外的时 间和内存开销。
AWQ 和尺度估计算法可以一起或单独应用。为此,请将相应的参数与数据集一起提供给 4 位 OVWeightQuantizationConfig
。例如
quantization_config = OVWeightQuantizationConfig(bits=4, sym=False, ratio=0.8, quant_method="awq", scale_estimation=True, dataset="wikitext2")
静态量化
在应用训练后静态量化时,权重和激活都将被量化。为了对激活应用量化,需要一个额外的校准步骤,该步骤包括向网络馈送一个calibration_dataset
,以估计量化激活参数。
以下是如何在给定您自己的calibration_dataset
的情况下,对微调的 DistilBERT 应用静态量化。
from transformers import AutoTokenizer
from optimum.intel import OVQuantizer, OVModelForSequenceClassification, OVConfig, OVQuantizationConfig
model_id = "distilbert-base-uncased-finetuned-sst-2-english"
model = OVModelForSequenceClassification.from_pretrained(model_id, export=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)
# The directory where the quantized model will be saved
save_dir = "ptq_model"
quantizer = OVQuantizer.from_pretrained(model)
# Apply static quantization and export the resulting quantized model to OpenVINO IR format
ov_config = OVConfig(quantization_config=OVQuantizationConfig())
quantizer.quantize(ov_config=ov_config, calibration_dataset=calibration_dataset, save_directory=save_dir)
# Save the tokenizer
tokenizer.save_pretrained(save_dir)
可以使用您的OVQuantizer
轻松创建校准数据集。
from functools import partial
def preprocess_function(examples, tokenizer):
return tokenizer(examples["sentence"], padding="max_length", max_length=128, truncation=True)
# Create the calibration dataset used to perform static quantization
calibration_dataset = quantizer.get_calibration_dataset(
"glue",
dataset_config_name="sst2",
preprocess_function=partial(preprocess_function, tokenizer=tokenizer),
num_samples=300,
dataset_split="train",
)
quantize()
方法应用训练后静态量化,并将生成的量化模型导出到 OpenVINO 中间表示 (IR)。生成的图以两个文件表示:一个 XML 文件描述网络拓扑,另一个二进制文件描述权重。生成的模型可以在任何目标英特尔设备上运行。
混合量化
传统的优化方法,如训练后 8 位量化,不适用于 Stable Diffusion (SD) 模型,会导致生成结果较差。另一方面,当应用于 Stable Diffusion 模型时,权重压缩不会显着提高性能,因为激活的大小与权重相当。U-Net 组件占管道整体执行时间的大部分。因此,仅优化此组件可以在推理速度方面带来可观的收益,同时在不进行微调的情况下保持可接受的精度。量化扩散管道的其余部分不会显着提高推理性能,但可能会导致显着的精度下降。因此,建议对 U-Net 模型以混合模式应用量化,对管道其余组件应用仅权重量化。
- U-Net:对权重和激活都应用量化
- 文本编码器、VAE 编码器/解码器:对权重应用量化
混合模式涉及对 MatMul 和 Embedding 层中的权重进行量化,以及对其他层中的激活进行量化,在优化后促进精度保留,同时减小模型大小。
quantization_config
用于定义优化 SD 管道的优化参数。要启用混合量化,请在quantization_config
中指定量化数据集。如果未定义数据集,则将对所有组件应用仅权重量化。
from optimum.intel import OVStableDiffusionPipeline, OVWeightQuantizationConfig
model = OVStableDiffusionPipeline.from_pretrained(
model_id,
export=True,
quantization_config=OVWeightQuantizationConfig(bits=8, dataset="conceptual_captions"),
)
有关更多详细信息,请参阅相应的 NNCF 文档。
训练时
除了像上面训练后量化一样在训练后优化模型之外,optimum.openvino
还提供了训练期间的优化方法,即量化感知训练 (QAT) 和联合剪枝、量化和蒸馏 (JPQD)。
训练时优化方法已弃用,将在 optimum-intel v1.22.0 中删除。
量化感知训练 (QAT)
QAT 在训练期间模拟量化的影响,以减轻其对模型精度的影响。如果训练后量化导致精度大幅下降,建议使用这种方法。以下是如何在应用量化感知训练 (QAT) 的同时,在 sst-2 任务上微调 DistilBERT 的示例。
import evaluate
import numpy as np
from transformers import (
AutoModelForSequenceClassification,
AutoTokenizer,
TrainingArguments,
default_data_collator,
)
from datasets import load_dataset
- from transformers import Trainer
+ from optimum.intel import OVConfig, OVTrainer, OVModelForSequenceClassification
model_id = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)
# The directory where the quantized model will be saved
save_dir = "qat_model"
dataset = load_dataset("glue", "sst2")
dataset = dataset.map(
lambda examples: tokenizer(examples["sentence"], padding=True), batched=True
)
metric = evaluate.load("glue", "sst2")
def compute_metrics(eval_preds):
preds = np.argmax(eval_preds.predictions, axis=1)
return metric.compute(predictions=preds, references=eval_preds.label_ids)
# Load the default quantization configuration detailing the quantization we wish to apply
+ ov_config = OVConfig()
- trainer = Trainer(
+ trainer = OVTrainer(
model=model,
args=TrainingArguments(save_dir, num_train_epochs=1.0, do_train=True, do_eval=True),
train_dataset=dataset["train"].select(range(300)),
eval_dataset=dataset["validation"],
compute_metrics=compute_metrics,
tokenizer=tokenizer,
data_collator=default_data_collator,
+ ov_config=ov_config,
+ task="text-classification",
)
# Train the model while applying quantization
train_result = trainer.train()
metrics = trainer.evaluate()
# Export the quantized model to OpenVINO IR format and save it
trainer.save_model()
# Load the resulting quantized model
- model = AutoModelForSequenceClassification.from_pretrained(save_dir)
+ model = OVModelForSequenceClassification.from_pretrained(save_dir)
联合剪枝、量化和蒸馏 (JPQD)
除了量化之外,剪枝和蒸馏等压缩方法在进一步提高任务性能和效率方面也很常见。结构化剪枝使模型变得更精简,以降低计算需求,而蒸馏则利用教师的知识(通常是更大的模型)来改进模型预测。将这些方法与量化结合可以生成具有显着效率改进的优化模型,同时享受良好的任务精度保留。在optimum.openvino
中,OVTrainer
提供了在训练期间联合剪枝、量化和蒸馏模型的功能。以下是如何在 sst-2 任务上对 BERT-base 进行优化的示例。
首先,我们创建一个配置字典来指定目标算法。由于optimum.openvino
依赖于 NNCF 作为后端,因此配置格式遵循 NNCF 规范(请参阅此处)。在下面的示例配置中,我们以列表形式指定了剪枝和量化及其超参数。剪枝方法与Lagunas 等人,2021,用于加速 Transformer 的块剪枝的工作非常相似,而量化则指的是 QAT。使用此配置,优化后的模型将在训练开始时使用剪枝和量化算子进行初始化。
compression_config = [
{
"compression":
{
"algorithm": "movement_sparsity",
"params": {
"warmup_start_epoch": 1,
"warmup_end_epoch": 4,
"importance_regularization_factor": 0.01,
"enable_structured_masking": True
},
"sparse_structure_by_scopes": [
{"mode": "block", "sparse_factors": [32, 32], "target_scopes": "{re}.*BertAttention.*"},
{"mode": "per_dim", "axis": 0, "target_scopes": "{re}.*BertIntermediate.*"},
{"mode": "per_dim", "axis": 1, "target_scopes": "{re}.*BertOutput.*"},
],
"ignored_scopes": ["{re}.*NNCFEmbedding", "{re}.*pooler.*", "{re}.*LayerNorm.*"]
}
},
{
"algorithm": "quantization",
"weights": {"mode": "symmetric"}
"activations": { "mode": "symmetric"},
}
]
已知限制:当前具有移动稀疏性的结构化剪枝仅支持BERT、Wav2vec2 和 Swin系列模型。有关更多信息,请参阅此处。
一旦我们准备好了配置,我们就可以开始开发训练管道,如下面的代码片段所示。由于我们使用上面的配置定制了联合压缩,请注意OVConfig
是用配置字典初始化的(为简洁起见,省略了 JSON 解析到 python 字典)。至于蒸馏,用户需要加载教师模型,这就像使用 Transformers API 加载普通模型一样。OVTrainingArguments
通过蒸馏超参数扩展了 Transformers 的TrainingArguments
,即蒸馏权重和温度,以便于使用。下面的代码片段展示了我们如何加载教师模型并使用OVTrainingArguments
创建训练参数。随后,将教师模型以及实例化的OVConfig
和OVTrainingArguments
提供给OVTrainer
。瞧!这就是我们需要做的全部,其余管道与本机 Transformers 训练相同。
- from transformers import Trainer, TrainingArguments
+ from optimum.intel import OVConfig, OVTrainer, OVTrainingArguments
# Load teacher model
+ teacher_model = AutoModelForSequenceClassification.from_pretrained(teacher_model_or_path)
- ov_config = OVConfig()
+ ov_config = OVConfig(compression=compression_config)
trainer = OVTrainer(
model=model,
+ teacher_model=teacher_model,
- args=TrainingArguments(save_dir, num_train_epochs=1.0, do_train=True, do_eval=True),
+ args=OVTrainingArguments(save_dir, num_train_epochs=1.0, do_train=True, do_eval=True, distillation_temperature=3, distillation_weight=0.9),
train_dataset=dataset["train"].select(range(300)),
eval_dataset=dataset["validation"],
compute_metrics=compute_metrics,
tokenizer=tokenizer,
data_collator=default_data_collator,
+ ov_config=ov_config,
task="text-classification",
)
# Train the model like usual, internally the training is applied with pruning, quantization and distillation
train_result = trainer.train()
metrics = trainer.evaluate()
# Export the quantized model to OpenVINO IR format and save it
trainer.save_model()
有关移动稀疏性的描述和配置方法,请参阅 NNCF 文档此处。
有关 NNCF 中可用的算法,请参阅文档此处。
有关完整的 JPQD 脚本,请参阅提供的示例此处。
量化感知训练 (QAT) 和知识蒸馏也可以结合起来,以优化 Stable Diffusion 模型,同时保持精度。有关更多详细信息,请查看此博客文章。
使用 Transformers 管道进行推理
在对模型应用量化后,我们就可以使用我们的OVModelFor<Task>
类轻松加载它,并使用 Transformers 的管道使用 OpenVINO 运行时执行推理。
from transformers import pipeline
from optimum.intel import OVModelForSequenceClassification
model_id = "helenai/distilbert-base-uncased-finetuned-sst-2-english-ov-int8"
ov_model = OVModelForSequenceClassification.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)
cls_pipe = pipeline("text-classification", model=ov_model, tokenizer=tokenizer)
text = "He's a dreadful magician."
outputs = cls_pipe(text)
[{'label': 'NEGATIVE', 'score': 0.9840195178985596}]