Press "Enter" to skip to content

使用Optimum和Transformers管道加速推理

推理已经在Optimum中登陆,并支持Hugging Face Transformers管道,包括使用ONNX Runtime进行文本生成。

BERT和Transformers的采用继续增长。基于Transformer的模型不仅在自然语言处理中实现了最先进的性能,还在计算机视觉、语音和时间序列中取得了突破。💬 🖼 🎤 ⏳

公司现在正从实验和研究阶段转向生产阶段,以便在大规模工作负载中使用Transformer模型。但是,默认情况下,BERT及其相关模型相对于传统的机器学习算法而言速度较慢、体积较大且复杂。

为了解决这个挑战,我们创建了Optimum – Hugging Face Transformers的扩展,以加速像BERT这样的Transformer模型的训练和推理。

在本博客文章中,您将学到:

  • 1. 什么是Optimum?ELI5
  • 2. 新的Optimum推理和管道功能
  • 3. 加速RoBERTa进行问答的端到端教程,包括量化和优化
  • 4. 当前限制
  • 5. Optimum推理常见问题解答
  • 6. 下一步是什么?

让我们开始吧!🚀

1. 什么是Optimum?ELI5

Hugging Face Optimum是一个开源库,是Hugging Face Transformers的扩展,提供了一套性能优化工具的统一API,以实现在加速硬件上训练和运行模型的最大效率,包括优化性能在Graphcore IPU和Habana Gaudi上的工具包。Optimum可以用于加速训练、量化、图优化,现在还支持transformers管道的推理。

2. 新的Optimum推理和管道功能

随着Optimum 1.2的发布,我们正在添加对推理和transformers管道的支持。这使得Optimum用户可以利用与transformers相同的API,同时使用加速运行时(如ONNX Runtime)的强大功能。

从Transformers切换到Optimum推理 Optimum推理模型与Hugging Face Transformers模型兼容。这意味着您只需用Optimum中对应的ORTModelForXxx类替换您的AutoModelForXxx类。例如,您可以在Optimum中使用问答模型的方式如下:

from transformers import AutoTokenizer, pipeline
-from transformers import AutoModelForQuestionAnswering
+from optimum.onnxruntime import ORTModelForQuestionAnswering

-model = AutoModelForQuestionAnswering.from_pretrained("deepset/roberta-base-squad2") # pytorch checkpoint
+model = ORTModelForQuestionAnswering.from_pretrained("optimum/roberta-base-squad2") # onnx checkpoint
tokenizer = AutoTokenizer.from_pretrained("deepset/roberta-base-squad2")

optimum_qa = pipeline("question-answering", model=model, tokenizer=tokenizer)

question = "我的名字是什么?"
context = "我的名字是菲利普,我住在纽伦堡。"
pred = optimum_qa(question, context)

在第一个版本中,我们添加了对ONNX Runtime的支持,但还有更多功能即将推出!这些新的ORTModelForXX现在可以与transformers管道一起使用。它们也完全集成到Hugging Face Hub中,以从社区中推送和拉取优化的检查点。除此之外,您还可以使用ORTQuantizer和ORTOptimizer先量化和优化您的模型,然后对其进行推理。请查看有关加速RoBERTa进行问答的端到端教程,包括量化和优化的详细信息。

3. 加速RoBERTa进行问答的端到端教程,包括量化和优化

在这个加速RoBERTa进行问答的端到端教程中,您将学习如何:

  1. 安装Optimum以用于ONNX Runtime
  2. 将Hugging Face Transformers模型转换为ONNX以进行推理
  3. 使用ORTOptimizer优化模型
  4. 使用ORTQuantizer应用动态量化
  5. 使用transformers管道进行加速推理
  6. 评估性能和速度

让我们开始吧 🚀

本教程是在一个 m5.xlarge AWS EC2 实例上创建和运行的。

3.1 安装 Optimum for Onnxruntime

我们的第一步是安装 Optimumonnxruntime 工具。

pip install "optimum[onnxruntime]==1.2.0"

这将为我们安装所有必需的软件包,包括 transformerstorchonnxruntime。如果您要使用 GPU,可以使用 pip install optimum[onnxruntime-gpu] 安装 optimum。

3.2 将 Hugging Face Transformers 模型转换为 ONNX 进行推理

在进行优化之前,我们需要将原始的 transformers 模型转换为 onnx 格式。为此,我们将使用新的 ORTModelForQuestionAnswering 类,并调用 from_pretrained() 方法,并设置 from_transformers 属性。我们使用的模型是 deepset/roberta-base-squad2,在 SQUAD2 数据集上经过微调的 RoBERTa 模型,取得了 F1 分数为 82.91,并使用的功能(任务)为 question-answering

from pathlib import Path
from transformers import AutoTokenizer, pipeline
from optimum.onnxruntime import ORTModelForQuestionAnswering

model_id = "deepset/roberta-base-squad2"
onnx_path = Path("onnx")
task = "question-answering"

# 加载原始的 transformers 模型并转换为 onnx
model = ORTModelForQuestionAnswering.from_pretrained(model_id, from_transformers=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 保存 onnx 检查点和 tokenizer
model.save_pretrained(onnx_path)
tokenizer.save_pretrained(onnx_path)

# 使用 transformers pipeline 测试模型,对于 squad_v2 使用 handle_impossible_answer
optimum_qa = pipeline(task, model=model, tokenizer=tokenizer, handle_impossible_answer=True)
prediction = optimum_qa(question="What's my name?", context="My name is Philipp and I live in Nuremberg.")

print(prediction)
# {'score': 0.9041663408279419, 'start': 11, 'end': 18, 'answer': 'Philipp'}

我们成功将原始的 transformers 模型转换为 onnx,并使用 transformers.pipelines 运行了第一个预测。现在让我们进行优化。🏎

如果您想了解有关导出 transformers 模型的更多信息,请查看文档:导出 🤗 Transformers 模型

3.3 使用 ORTOptimizer 优化模型

在将我们的 onnx 检查点保存到 onnx/ 后,我们现在可以使用 ORTOptimizer 应用图优化,例如运算符融合和常量折叠,以加速延迟和推理。

from optimum.onnxruntime import ORTOptimizer
from optimum.onnxruntime.configuration import OptimizationConfig

# 创建 ORTOptimizer 并定义优化配置
optimizer = ORTOptimizer.from_pretrained(model_id, feature=task)
optimization_config = OptimizationConfig(optimization_level=99) # 启用所有优化

# 将优化配置应用于模型
optimizer.export(
    onnx_model_path=onnx_path / "model.onnx",
    onnx_optimized_model_output_path=onnx_path / "model-optimized.onnx",
    optimization_config=optimization_config,
)

为了测试性能,我们可以再次使用 ORTModelForQuestionAnswering 类,并提供额外的 file_name 参数来加载我们优化后的模型。(这对于 hub 上可用的模型也适用)

from optimum.onnxruntime import ORTModelForQuestionAnswering

# 加载量化模型
opt_model = ORTModelForQuestionAnswering.from_pretrained(onnx_path, file_name="model-optimized.onnx")

# 使用 transformers pipeline 测试量化模型
opt_optimum_qa = pipeline(task, model=opt_model, tokenizer=tokenizer, handle_impossible_answer=True)
prediction = opt_optimum_qa(question="What's my name?", context="My name is Philipp and I live in Nuremberg.")
print(prediction)
# {'score': 0.9041663408279419, 'start': 11, 'end': 18, 'answer': 'Philipp'}

我们将在步骤3.6中详细评估性能和速度的变化。

3.4 使用ORTQuantizer应用动态量化

在优化了模型后,我们可以通过使用ORTQuantizer对其进行量化来进一步加速。ORTOptimizer可以用于应用动态量化以减小模型大小并加速延迟和推断。

由于实例由支持avx512的Intel Cascade Lake CPU提供动力,我们使用avx512_vnni

from optimum.onnxruntime import ORTQuantizer
from optimum.onnxruntime.configuration import AutoQuantizationConfig

# 创建ORTQuantizer并定义量化配置
quantizer = ORTQuantizer.from_pretrained(model_id, feature=task)
qconfig = AutoQuantizationConfig.avx512_vnni(is_static=False, per_channel=True)

# 将量化配置应用于模型
quantizer.export(
    onnx_model_path=onnx_path / "model-optimized.onnx",
    onnx_quantized_model_output_path=onnx_path / "model-quantized.onnx",
    quantization_config=qconfig,
)

现在我们可以比较这个模型的大小以及一些延迟性能。

import os
# 获取模型文件大小
size = os.path.getsize(onnx_path / "model.onnx")/(1024*1024)
print(f"原始 Onnx 模型文件大小: {size:.2f} MB")
size = os.path.getsize(onnx_path / "model-quantized.onnx")/(1024*1024)
print(f"量化后的 Onnx 模型文件大小: {size:.2f} MB")

# 原始 Onnx 模型文件大小: 473.31 MB
# 量化后的 Onnx 模型文件大小: 291.77 MB

使用Optimum和Transformers管道加速推理 四海 第1张

我们将模型的大小减小了近50%,从473MB减小到291MB。要运行推理,我们可以再次使用ORTModelForQuestionAnswering类,并提供一个额外的file_name参数来加载我们的量化模型。(这对于Hub上可用的模型也适用)

# 加载量化模型
quantized_model = ORTModelForQuestionAnswering.from_pretrained(onnx_path, file_name="model-quantized.onnx")

# 使用 transformers pipeline 测试量化模型
quantized_optimum_qa = pipeline(task, model=quantized_model, tokenizer=tokenizer, handle_impossible_answer=True)
prediction = quantized_optimum_qa(question="我的名字是什么?", context="我的名字是Philipp,我住在纽伦堡。")
print(prediction)
# {'score': 0.9246969819068909, 'start': 11, 'end': 18, 'answer': 'Philipp'}

很好!模型预测了相同的答案。

3.5 使用 Transformers pipelines 进行加速推理

Optimum 内置了对 transformers pipelines 的支持。这使我们能够利用与使用 PyTorch 和 TensorFlow 模型相同的 API。我们已经在步骤3.2、3.3和3.4中使用了此功能来测试我们转换和优化的模型。在撰写本文时,我们支持 ONNX Runtime,并将来还会有更多。下面是如何使用 transformers pipelines 的示例。

from transformers import AutoTokenizer, pipeline
from optimum.onnxruntime import ORTModelForQuestionAnswering

tokenizer = AutoTokenizer.from_pretrained(onnx_path)
model = ORTModelForQuestionAnswering.from_pretrained(onnx_path)

optimum_qa = pipeline("question-answering", model=model, tokenizer=tokenizer)
prediction = optimum_qa(question="我的名字是什么?", context="我的名字是Philipp,我住在纽伦堡。")

print(prediction)
# {'score': 0.9041663408279419, 'start': 11, 'end': 18, 'answer': 'Philipp'}

除此之外,我们还为 Optimum 添加了一个pipelines API,以确保您的加速模型更加安全。这意味着如果您尝试使用不受支持的模型或任务来使用optimum.pipelines,您将看到一个错误。您可以将optimum.pipelines用作transformers.pipelines的替代。

from transformers import AutoTokenizer
from optimum.onnxruntime import ORTModelForQuestionAnswering
from optimum.pipelines import pipeline

tokenizer = AutoTokenizer.from_pretrained(onnx_path)
model = ORTModelForQuestionAnswering.from_pretrained(onnx_path)

optimum_qa = pipeline("question-answering", model=model, tokenizer=tokenizer, handle_impossible_answer=True)
prediction = optimum_qa(question="我的名字是什么?", context="我的名字是Philipp,我住在纽伦堡。")

print(prediction)
# {'score': 0.9041663408279419, 'start': 11, 'end': 18, 'answer': 'Philipp'}

3.6 评估性能和速度

在这个关于加速RoBERTa进行问答的端到端教程中,包括量化和优化,我们创建了3个不同的模型。一个普通的转换模型,一个优化模型和一个量化模型。

作为教程的最后一步,我们想详细了解模型的性能和准确性。应用优化技术,如图形优化或量化,不仅会影响性能(延迟),还可能对模型的准确性产生影响。因此,加速模型是有一个权衡的。

让我们评估我们的模型。我们的transformers模型deepset/roberta-base-squad2在SQUAD2数据集上进行了微调。这将是我们用来评估模型的数据集。

from datasets import load_metric,load_dataset

metric = load_metric("squad_v2")
dataset = load_dataset("squad_v2")["validation"]

print(f"数据集长度 {len(dataset)}")
#数据集长度 11873

现在我们可以利用datasets的map函数迭代SQUAD2验证集中的每个数据点并运行预测。因此,我们编写一个evaluate辅助方法,该方法使用我们的pipelines并应用一些转换以与squad v2指标一起工作。

这可能需要一些时间(1.5小时)

def evaluate(example):
  default = optimum_qa(question=example["question"], context=example["context"])
  optimized = opt_optimum_qa(question=example["question"], context=example["context"])
  quantized = quantized_optimum_qa(question=example["question"], context=example["context"])
  return {
      'reference': {'id': example['id'], 'answers': example['answers']},
      'default': {'id': example['id'],'prediction_text': default['answer'], 'no_answer_probability': 0.},
      'optimized': {'id': example['id'],'prediction_text': optimized['answer'], 'no_answer_probability': 0.},
      'quantized': {'id': example['id'],'prediction_text': quantized['answer'], 'no_answer_probability': 0.},
      }

result = dataset.map(evaluate)
# 取消注释以在数据集的2000子集上运行评估
# result = dataset.shuffle().select(range(2000)).map(evaluate)

现在让我们比较结果

default_acc = metric.compute(predictions=result["default"], references=result["reference"])
optimized = metric.compute(predictions=result["optimized"], references=result["reference"])
quantized = metric.compute(predictions=result["quantized"], references=result["reference"])

print(f"普通模型: 精确度={default_acc['exact']}% f1={default_acc['f1']}%")
print(f"优化模型: 精确度={optimized['exact']}% f1={optimized['f1']}%")
print(f"量化模型: 精确度={quantized['exact']}% f1={quantized['f1']}%")

# 普通模型: 精确度=79.07858165585783% f1=82.14970024570314%
# 优化模型: 精确度=79.07858165585783% f1=82.14970024570314%
# 量化模型: 精确度=78.75010528088941% f1=81.82526107204629%

我们的优化和量化模型实现了78.75%的精确匹配和81.83%的f1分数,这是原始准确性的99.61%。达到原始模型的99%非常好,尤其是我们使用了动态量化。

好了,现在让我们测试我们优化和量化模型的性能(延迟)。

但首先,让我们将我们的上下文和问题扩展到更有意义的序列长度为128。

context="你好,我叫Philipp,我住在德国纽伦堡。目前我在Hugging Face担任技术主管,通过开源和开放科学来使人工智能民主化。在过去,我为金融科技和保险公司设计和实施了云原生机器学习架构。我5年前发现了对云概念和机器学习的热情。从那时起,我就没有停止学习。目前,我专注于NLP领域以及如何利用BERT、Roberta、T5、ViT和GPT2等模型来带来商业价值。"
question="Philipp是以什么身份工作的?"

为了简单起见,我们将使用一个Python循环来计算我们的原始模型和优化和量化模型的平均延迟。

from time import perf_counter
import numpy as np

def measure_latency(pipe):
    latencies = []
    # 预热
    for _ in range(10):
        _ = pipe(question=question, context=context)
    # 定时运行
    for _ in range(100):
        start_time = perf_counter()
        _ =  pipe(question=question, context=context)
        latency = perf_counter() - start_time
        latencies.append(latency)
    # 计算运行统计数据
    time_avg_ms = 1000 * np.mean(latencies)
    time_std_ms = 1000 * np.std(latencies)
    return f"平均延迟 (毫秒) - {time_avg_ms:.2f} +\- {time_std_ms:.2f}"

print(f"原始模型 {measure_latency(optimum_qa)}")
print(f"优化和量化模型 {measure_latency(quantized_optimum_qa)}")

# 原始模型 平均延迟 (毫秒) - 117.61 +\- 8.48
# 优化和量化模型 平均延迟 (毫秒) - 64.94 +\- 3.65

使用Optimum和Transformers管道加速推理 四海 第2张

我们成功将模型的延迟从117.61ms加速到64.94ms,大约提高了2倍,同时保持了99.61%的准确率。需要记住的一点是,我们使用了一个具有2个物理核心的中性能CPU实例。通过切换到GPU或更高性能的CPU实例,例如基于冰湖的实例,可以将延迟降低到几毫秒。

4. 当前限制

我们刚刚开始支持在https://github.com/huggingface/optimum中进行推理,所以我们也想分享当前的限制。所有这些限制都在路线图上,并将在不久的将来得到解决。

  • 远程模型 > 2GB: 目前,只能加载小于2GB的模型 从Hugging Face Hub。我们正在努力添加对大于2GB的模型/多文件模型的支持。
  • Seq2Seq任务/模型: 我们不支持seq2seq任务,比如摘要和T5等模型,主要是由于单一模型支持的限制。但我们正在积极努力解决这个问题,为您提供在transformers中熟悉的相同体验。
  • 过去的键值: 生成模型,例如GPT-2使用了称为过去键值的东西,它们是预先计算的注意力块的键值对,可以用于加速解码。目前,ORTModelForCausalLM没有使用过去的键值。
  • 无缓存: 当加载优化模型(*.onnx)时,它不会被本地缓存。

5. Optimum Inference常见问题解答

支持哪些任务?

您可以在文档中找到所有支持的任务列表。目前支持的管道任务有特征提取文本分类标记分类问答零样本分类文本生成

支持哪些模型?

任何可以使用transformers.onnx导出并具有支持的任务的模型都可以使用,其中包括BERT、ALBERT、GPT2、RoBERTa、XLM-RoBERTa、DistilBERT等。

支持哪些运行时?

目前,ONNX Runtime得到支持。我们正在努力添加更多的运行时。如果您对特定的运行时感兴趣,请告诉我们。

如何使用Optimum与Transformers?

您可以在我们的文档中找到示例和说明。

如何使用GPU?

要能够使用GPU,您只需安装optimum[onnxruntine-gpu],它将安装所需的GPU提供程序并默认使用它们。

如何在管道中使用量化和优化的模型?

您可以使用新的ORTModelForXXX类使用from_pretrained方法加载优化或量化的模型。您可以在我们的文档中了解更多信息。

6. 接下来是什么?

您问Optimum的下一步是什么?有很多事情。我们专注于将Optimum打造成与transformers加速和优化相关的参考开源工具包。为了实现这一目标,我们将解决当前的限制,改进文档,创建更多内容和示例,并推动加速和优化transformers的极限。

在Optimum的路线图中,一些重要的特性包括:

  • 支持语音模型(Wav2vec2)和语音任务(自动语音识别)
  • 支持视觉模型(ViT)和视觉任务(图像分类)
  • 通过添加对OrtValue和IOBinding的支持来改善性能
  • 更容易地评估加速模型
  • 添加对TensorRT和AWS-Neuron等其他运行时和提供程序的支持

感谢阅读!如果您对加速Transformers,使其高效并将其扩展到数十亿个请求的工作感到兴奋,那么您应该申请加入我们。

如果您有任何问题,请随时通过Github或论坛与我联系。您还可以在Twitter或LinkedIn上与我联系。

Leave a Reply

Your email address will not be published. Required fields are marked *