使用 Optimum Intel 和 OpenVINO™ 加速 Hugging Face Transformer 模型的推理

2024年8月8日 257点热度 0人点赞 0条评论

1. 简介

Hugging Face是一个大型开源社区,它迅速成为自然语言处理 (NLP)、自动语音识别 (ASR) 和计算机视觉 (CV) 领域的预训练深度学习模型的诱人中心。

Optimum Intel提供了一个简单的界面来优化 Transformer 模型并将其转换为OpenVINO™中间表示 (IR) 格式,以使用 OpenVINO™ 运行时加速英特尔® 架构上的端到端管道。

情感分类(sentimental classification)是流行的 NLP 任务之一,是识别文本中的观点并将其标记为正面或负面的自动化过程。在本博客中,我们使用 DistilBERT 进行情感分类任务作为示例,展示 Optimum Intel 如何帮助使用神经网络压缩框架 (NNCF)优化模型并使用 OpenVINO™ 运行时加速推理。

2. 设置环境

在新的 Python 虚拟环境中安装 optimum-intel 及其依赖项,如下所示:

conda create -n optimum-intel python=3.8
conda activate optimum-intel
python -m pip install torch==1.9.1 onnx py-cpuinfo
python -m pip install optimum[openvino,nncf]

3. 使用 OpenVINO™ Runtime 进行模型推理

Optimum 推理模型与 Hugging Face Transformers 模型的 API 兼容;这意味着您只需将 Hugging Face Transformer 的“AutoModelXXX”类替换为“OVModelXXX”类,即可切换模型推理与 OpenVINO™ 运行时。您可以在使用 from_pretrained() 方法加载模型时设置“from_transformers=True”,加载的模型将自动转换为 OpenVINO™ IR,以便使用 OpenVINO™ 运行时进行推理。

下面是如何使用 OpenVINO™ 运行时对情感分类任务进行推理的示例,管道的输出由分类标签(正/负)和相应的置信度组成。

from optimum.intel.openvino import OVModelForSequenceClassification
from transformers import AutoTokenizer, pipeline

model_id = "distilbert-base-uncased-finetuned-sst-2-english"
hf_model = OVModelForSequenceClassification.from_pretrained(
    model_id, from_transformers=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)
hf_pipe_cls = pipeline("text-classification",
                       model=hf_model, tokenizer=tokenizer)
text = "He's a dreadful magician."
fp32_outputs = hf_pipe_cls(text)
print("FP32 model outputs: ", fp32_outputs)

4. 使用 NNCF 框架进行模型量化

大多数深度学习模型都是使用 32 位浮点精度 (FP32) 构建的。量化是使用较少内存以最小精度损失表示模型的过程。为了通过英特尔® 深度学习加速进一步优化英特尔® 架构上的模型性能,需要将模型量化为 8 位整数精度 (INT8)。

Optimum Intel 支持使用 NNCF 对 Hugging Face Transformer 模型进行量化。NNCF 提供两种主流量化方法 – 训练后量化 (PTQ) 和量化感知训练 (QAT)。

  • 训练后量化(PTQ)是指使用代表性校准数据集对模型进行量化,而无需进行微调。
  • 量化感知训练(QAT)用于模拟训练过程中量化的影响,以减轻其对模型准确性的影响

4.1. 使用 NNCF PTQ 进行模型量化

NNCF 训练后静态量化引入了一个额外的校准步骤,其中数据通过网络输入以计算激活量化参数。以下是使用通用语言理解评估 (GLUE) 数据集作为校准数据集对预训练的 DistilBERT 应用静态量化的方法:

from functools import partial
from optimum.intel.openvino import OVQuantizer, OVConfig
from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_id = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)

def preprocess_fn(examples, tokenizer):
    return tokenizer(
        examples["sentence"], padding=True, truncation=True, max_length=128
    )

quantizer = OVQuantizer.from_pretrained(model)
calibration_dataset = quantizer.get_calibration_dataset(
    "glue",
    dataset_config_name="sst2",
    preprocess_function=partial(preprocess_fn, tokenizer=tokenizer),
    num_samples=100,
    dataset_split="train",
    preprocess_batch=True,
)

# Load the default quantization configuration
ov_config = OVConfig()

# The directory where the quantized model will be saved
save_dir = "nncf_ptq_results"
# Apply static quantization and save the resulting model in the OpenVINO IR format
quantizer.quantize(calibration_dataset=calibration_dataset,
                   save_directory=save_dir, quantization_config=ov_config)

quantize() 方法应用训练后静态量化,并将生成的量化模型导出到 OpenVINO™ 中间表示 (IR),该模型可部署在任何目标英特尔® 架构上。

4.2. 使用 NNCF QAT 进行模型量化

量化感知训练 (QAT) 旨在通过模拟训练期间量化的影响来缓解模型准确性问题。如果训练后量化导致准确性下降,则可以使用 QAT。

NNCF 提供了一个“OVTrainer”类来替代 Hugging Face Transformer 的“Trainer”类,以便在训练期间通过额外的量化配置实现量化。以下是如何在应用量化感知训练 (QAT) 的同时使用斯坦福情绪树库 (SST) 数据集对 DistilBERT 进行微调的示例:

import numpyThe  as np
import evaluate
from datasets import load_dataset
from transformers import AutoModelForSequenceClassification, AutoTokenizer, TrainingArguments, default_data_collator
from optimum.intel.openvino import OVConfig, OVTrainer

model_id = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)
dataset = load_dataset("glue", "sst2")
dataset = dataset.map(
    lambda examples: tokenizer(examples["sentence"], padding=True, truncation=True, max_length=128), batched=True
)
metric = evaluate.load("accuracy")

def compute_metrics(p): return metric.compute(
    predictions=np.argmax(p.predictions, axis=1), references=p.label_ids
)

# The directory where the quantized model will be saved
save_dir = "nncf_qat_results"

# Load the default quantization configuration
ov_config = OVConfig()

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,
    feature="sequence-classification",
)
train_result = trainer.train()
metrics = trainer.evaluate()
trainer.save_model()

4.3. FP32 与 INT8 模型输出比较

“OVModelForXXX” 类提供了相同的 API,通过设置“from_transformers=False”来加载 FP32 和量化 INT8 OpenVINO™ 模型。下面是一个如何加载由 NNCF 优化的量化 INT8 模型并使用 OpenVINO™ 运行时进行推理的示例。

ov_ptq_model = OVModelForSequenceClassification.from_pretrained(“nncf_ptq_results”, from_transformers=False)
ov_ptq_pipe_cls = pipeline("text-classification", model=ov_ptq_model, tokenizer=tokenizer)
ov_ptq_outputs = ov_ptq_pipe_cls(text)
print("PTQ quantized INT8 model outputs: ", ov_ptq_outputs)

ov_qat_model = OVModelForSequenceClassification.from_pretrained("nncf_qat_results", from_transformers=False)
ov_qat_pipe_cls = pipeline("text-classification", model=ov_qat_model, tokenizer=tokenizer)
ov_qat_outputs = ov_qat_pipe_cls(text)
print("QAT quantized INT8 model outputs: ", ov_qat_outputs)

以下是 FP32 和 INT8 模型的情感分类输出示例:

5. 缓解饱和引起的准确性问题

旧 CPU 代数(基于 SSE、AVX-2、AVX-512 指令集)的 8 位指令在计算点积时容易出现中间缓冲区所谓的饱和(溢出) ,而点积是卷积或 MatMul 运算的重要组成部分。在上述架构上运行 8 位量化模型的推理时,这种饱和会导致准确率下降。采用英特尔®深度学习加速 (VNNI) 技术及后续代数的GPU 或 CPU 不会出现此问题。

如果使用 NNCF 默认量化配置进行量化后准确度出现显著差异(>1%),则可以使用以下示例代码来检查部署的平台是否支持 Intel ® Deep Learning Boost (VNNI) 及后续版本:

import cpuinfo
flags = cpuinfo.get_cpu_info()['flags']
brand_raw = cpuinfo.get_cpu_info()['brand_raw']
w = "without"
overflow_fix = 'enable'
for flag in flags:
    if "vnni" in flag or "amx_int8" in flag:
        w = "with"
        overflow_fix = 'disable'
print("Detected CPU platform {0} {1} support of Intel(R) Deep Learning Boost (VNNI) technology \
    and further generations, overflow fix should be {2}d".format(brand_raw, w, overflow_fix))

虽然量化激活使用了全部 8 位数据类型,但有一种解决方法,即仅使用 7 位来表示权重(卷积层或连接层),以缓解旧 CPU 平台上许多模型的饱和问题。

NNCF 提供了三个选项来处理饱和问题。可以使用“overflow_fix”参数在 NNCF 量化配置中启用这些选项:

  • “disable”:(默认)选项根本不应用饱和修复
  • “enable”:适用于模型中所有层的选项
  • “first_layer_only”:用于修复第一层饱和问题的选项

以下是在量化配置中启用溢出修复以缓解旧 CPU 平台上的准确性问题的示例:

from optimum.intel.openvino.configuration import DEFAULT_QUANTIZATION_CONFIG

ov_config_dict = DEFAULT_QUANTIZATION_CONFIG
ov_config_dict["overflow_fix"] = "enable"
ov_config = OVConfig(compression=ov_config_dict)

使用 NNCF PTQ/NNCF 更新的量化配置对模型量化后,可以重复步骤 4.3 来验证量化后的 INT8 模型推理结果是否与 FP32 模型输出一致。

予人玫瑰,手有余香。如果您觉得本文对您有帮助,请点赞或打赏。

文章评论

您需要 登录 之后才可以评论