Optimum 文档

优化

您正在查看 main 版本,它需要从源代码安装。如果您想使用常规的 pip install,请查看最新的稳定版本 (v1.23.1).
Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始使用

优化

🤗 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_sizeratio 值通常会提高精度,但会牺牲模型大小和推理延迟。

通过采用以下数据相关方法之一,可以进一步提高 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创建训练参数。随后,将教师模型以及实例化的OVConfigOVTrainingArguments提供给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}]
< > 更新 在 GitHub 上