【模型推理加速系列】04:BERT加速方案对比 TorchScript vs. ONNX

部署运行你感兴趣的模型镜像

简介

本文以 BERT-base 的为例,介绍2种常用的推理加速方案:ONNX 和 TorchScript,并实测对比这两种加速方案与原始Pytorch模型格式的inference性能。本文通过实测发现:

(1)GPU环境中,小batch size场景下,ONNX表现最好;

(2)GPU环境中,大batch size场景下,TorchScript 性能最好;

(3)CPU(AMD)环境中,ONNX性能最好;

PS:

以上结论仅基于笔者自己的测试环境,具体测试细节请参考推理速度评测部分。
完整实验代码请在微信公众号小窗幽记机器学习 内回复【bert】获取下载链接。

基于ONNX

导出ONNX模型

关于BERT如何基于ONNX做推理加速可以参考之前的两篇小作文:如何用ONNX加速BERT特征抽取如何用ONNX加速BERT特征抽取-part2。本文在此只再次强调Pytorch模型导出ONNX模型过程中的2个关键点:

(1) 创建符合数据规范的伪输入(dummy inputs),并利用伪输入在模型中做一次前向inference。Pytorch模型转ONNX调用的接口是torch.onnx.export。该接口支持2种模式:tracing 和 scripting,默认模式是基于tracing(追踪)。本文这里导出BERT的ONNX格式也是使用tracing模式。在这个过程中会tracing(追踪)tensor的相关操作形成静态图。tracing方式不会捕获根据输入数据而改变的行为,比如对于if语句,只会记录执行的那一条分支,对于for循环的次数,则会导出与跟踪运行时完全相同的静态图。因此,如果待导出的模型推理阶段(forward函数)中存在判断和非固定循环次数时,Tracing模式并不适用。对于这类使用动态控制流导出模型,则需要使用Scripting模式导出,即使用torch.jit.script

(2) 选择和设置输入和输出的name及其动态轴。需要特别注意的是设置input_names顺序需要与实际相符合。比如BERT-base中文版这里的模型,如果变动input_names=['input_ids', 'attention_mask', 'token_type_ids']中的参数名顺序会有影响吗?此外,正常业务场景下,batch size 一般是变长的,所以会将输入参数中的第0维设置为动态。

示例代码

BERT 导出 ONNX具体示例代码如下:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   convert_2_onnx.py
@Time    :   2022/11/05 10:10:16
@Author  :   Jason
@Desc    :   Pytorch模型转为ONNX格式
'''
import torch
from transformers import AutoModel
model_path = "/share/model_zoo/bert-base-chinese/"
MODEL_ONNX_PATH = "/share/result_models/ner/bert/onnx/raw_bert_dynamic.onnx"
OPERATOR_EXPORT_TYPE = torch._C._onnx.OperatorExportTypes.ONNX
model = AutoModel.from_pretrained(model_path)
model.eval()

def make_train_dummy_input(seq_len):
    org_input_ids = torch.LongTensor([[i for i in range(seq_len)]])
    org_token_type_ids = torch.LongTensor([[1 for i in range(seq_len)]])
    org_input_mask = torch.LongTensor([[0 for i in range(int(seq_len/2))] + [1 for i in range(seq_len - int(seq_len/2))]])
    return (org_input_ids, org_token_type_ids, org_input_mask)
    
org_dummy_input = make_train_dummy_input(int(args.seq_len))
output = torch.onnx.export(model,
                           org_dummy_input,
                           MODEL_ONNX_PATH,
                           verbose=True,
                           operator_export_type=OPERATOR_EXPORT_TYPE,
                           opset_version=12,
                           input_names=['input_ids', 'attention_mask', 'token_type_ids'],  # 需要注意顺序!不可随意改变, 否则结果与预期不符
                           output_names=['last_hidden_state', 'pooler_output'],  # 需要注意顺序, 否则在推理阶段可能用错output_names
                           do_constant_folding=True,
                           dynamic_axes={"input_ids": {0: "batch_size", 1: "length"},
                                         "token_type_ids": {0: "batch_size", 1: "length"},
                                         "attention_mask": {0: "batch_size", 1: "length"},
                                         "pooler_output": {0: "batch_size"},
                                         "last_hidden_state": {0: "batch_size"}}
                           )
print("Export of {} complete!".format(MODEL_ONNX_PATH))

PS:

上述代码设置输入的文本长度和batch size 都是变长,即无需对输入做padding和攒到固定batch size,再做inference。

基于TorchScript

JIT

在介绍TorchScript之前,先介绍下JIT。因为Pytorch中TorchScript的使用是在torch.jit这个模块下。JIT 是一种概念,全称是 Just In Time Compilation,中文译为即时编译,是一种程序优化的方法。

在深度学习中 JIT 的思想更随处可见,最明显的就是 Keras 框架的 model.compile。PyTorch 从面世以来一直以易用性著称(使用eager模式设计),使用最贴合原生 Python 的开发方式。这得益于 PyTorch 的动态图结构。开发者可以在 PyTorch 的模型前向中加任何 Python 的流程控制语句, 甚至是断点单步跟进都不会有任何问题,但是如果是 TensorFlow,则需要使用 tf.cond 等 TensorFlow 自己开发的流程控制,甚是麻烦。

动态图模型通过牺牲一些高级特性来换取易用性,那到底 JIT 有哪些特性,在什么情况下不得不用到 JIT 呢?PyTorch JIT 模式就是一种将原本eager模式的表达转变并固定为一个计算图,便于进行优化和序列化。那TorchScript(PyTorch 的 JIT 实现) 到底带来了哪些好处?主要有以下3点:

  1. 模型部署
    PyTorch 的 1.0 版本发布的最核心的两个新特性就是 JIT 和 C++ API,这两个特性一起发布不是没有道理的,JIT 是 Python 和 C++ 的桥梁,开发者可以使用 Python 训练模型,然后通过 JIT 将模型转为语言无关的模块, 从而让 C++ 可以非常方便得调用,从此使用 Python 训练模型,使用 C++ 将模型部署到生产环境对 PyTorch 来说成为了一件很容易的事。而因为使用了 C++, 开发者现在几乎可以把 PyTorch 模型部署到任意平台和设备上:树莓派、iOS、Android 等。

  2. 性能提升
    JIT 既然是为部署生产所提供的特性,那免不了在性能上面做了极大的优化,如果inference的场景对性能要求高,则可以考虑将模型(torch.nn.Module)转换为 TorchScript Module,再进行inference。

  3. 模型可视化
    TensorFlow 或 Keras 对模型可视化工具(TensorBoard等)非常友好,因为本身就是静态图的编程模型,在模型定义好后整个模型的结构和前向推理逻辑就已经清楚了;但 PyTorch 本身是不支持的,所以 PyTorch 模型在可视化上一直表现得不好,但 JIT 改善了这一情况。现在可以使用 JIT 的 trace 功能来得到 PyTorch 模型针对某一输入的前向推理逻辑,通过前向推理逻辑可以得到模型大致的结构,但如果在 forward 方法中有很多条件控制语句,则需要使用 PyTorch JIT 中的Scripting 的方式,这两种方式的使用在后续文章中会单独详细介绍。

TorchScript

TorchScript 是PyTorch模型的一种中间表示(即常见的intermediate representation,IR),是nn.Module的子类,基于这种模型格式可以在高性能的环境如C++中运行。TorchScript这种PyTorch模型表示格式可以由 TorchScript 编译器理解、编译和序列化而成。从根本上说,TorchScript 本身就是一种编程语言。它是使用 PyTorch API 的 Python 的子集。简单来说,TorchScript 软件栈可以将 Python 代码转换成 C++ 代码。TorchScript 软件栈包括两部分:TorchScript(Python)和 LibTorch(C++)。TorchScript负责将 Python 代码转成一个模型文件,LibTorch负责解析运行这个模型文件。如果使用过Triton的小伙伴应该还记得配置文件中backend中一个选项是libtorch,正是对应这里的TorchScript模型格式的部署。

TorchScript 保存模型有两种模式:trace 模式和 script 模式。本文将BERT以trace模式导出 TorchScript

PS:目前以 script 模式导出BERT尚未成功,big sad~欢迎导出成功的小伙伴指教。

示例代码

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   convert_2_jit.py
@Time    :   2022/10/31 20:10:41
@Author  :   Jason
@Desc    :   Pytorch模型转为TorchScript格式
'''

import torch
import pdb
from transformers import BertModel, BertTokenizer, BertConfig, AutoTokenizer
device = "cuda"
is_use_gpu = False

model_path = "/share/model_zoo/bert-base-chinese/"
test_text = ["我是卖铁观音的小男孩,毕业于华中科技大学"]
tokenizer = BertTokenizer.from_pretrained(model_path)
inputs = tokenizer(test_text, max_length=32, return_tensors="pt")
config = BertConfig.from_pretrained(model_path, cache_dir=None, torchscript=True, use_cache=False)
model = BertModel.from_pretrained(model_path, config=config, cache_dir=None, )
if is_use_gpu:
    model = model.to(device)
    inputs = inputs.to(device)
model.eval()
print("inputs=", inputs)
temp = (inputs['input_ids'], inputs['attention_mask'], inputs['token_type_ids'], )
traced_model = torch.jit.trace(model, temp)
if is_use_gpu:
    torch.jit.save(traced_model, "traced_bert_gpu.pt")
else:
    torch.jit.save(traced_model, "traced_bert_cpu.pt")

推理速度评测

以下分享不同加速方案、在不同batch size下 在CPU和GPU上的推理耗时。

CPU

评测所使用CPU 型号:

128 AMD EPYC 7713 64-Core Processor

AMD EPYC 7713 型号CPU 不同batch size大小下的平均耗时(单位ms),即单个batch size的推理耗时(如果想要得到单个样本的推理耗时只需要再除以batch size即可):

batch sizebaselinejitONNX
11870220036.2
8861
161160

因为这款AMD型号的CPU在baseline 和 jit 上的推理性能实在是太差,放弃在baseline 和jit上的进一步评测。但是,可以得出一个结论,ONNX格式在该型号CPU上性能仍然较好,从而侧面看出ONNX模型格式在特殊场景下仍然具有一定优越性。

GPU

评测所使用GPU型号:

NVIDIA GeForce RTX 3090 GPU

NVIDIA GeForce RTX 3090 型号GPU 不同batch size大小下的平均耗时(单位ms),即单个batch size的推理耗时(如果想要得到单个样本的推理耗时只需要再除以batch size即可):

batch sizebaselinejitONNX
15.642
85.54.42.6
166.04.12.7
325.44.23.6
646.56.46.7
12811.110.812.7
25622.321.825.2

基于上述实验,可以得出以下结论:

  • batch size 较小时,ONNX性能显著优于其他方案
  • batch size 较大时, TorchScript格式(jit) ,显著优于其他方案,特别是ONNX方案。从而可以看出,ONNX不太适合于大 batch size场景。

需要特别强调的是,上述实验结果基于笔者特定软硬件环境评测所得。不同环境下的评测结果结论可能不同,欢迎小伙伴们基于自己的机器进行实测后分享实验结果。

附录

硬件信息:

CPU: AMD EPYC 7713(2个物理CPU,每个物理CPU中core的个数是64,逻辑CPU的个数是128)
GPU: NVIDIA GeForce RTX 3090(单卡)

核心包的版本:

onnxruntime-gpu Version: 1.13.1
transformers Version: 4.21.3
torch Version: 1.13.0a0+08820cb

您可能感兴趣的与本文相关的镜像

PyTorch 2.7

PyTorch 2.7

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

<think>我们正在讨论如何对BERT模型进行推理BERT(BidirectionalEncoderRepresentationsfromTransformers)是一种基于Transformer的预训练语言表示模型,广泛应用于自然语言处理任务中,如文本分类、问答、命名实体识别等。推理阶段指的是使用已经训练好的BERT模型对新的输入数据进行预测。根据提供的引用资料,我们可以从以下几个方面来回答:1.使用预训练的BERT模型进行推理的一般步骤2.推理加速技术(如TensorRT、TorchScript等)3.使用不同编程语言进行推理(如Python、Java)###1.BERT模型推理的一般步骤BERT模型推理通常包括以下步骤:-**准备输入数据**:将原始文本转换为模型可接受的输入格式。这通常包括分词(使用BERT的WordPiece分词器)、添加特殊标记(如[CLS]和[SEP])、生成输入ID、注意力掩码和段标记(对于句子对任务)。-**加载模型**:加载预训练好的BERT模型(可以是PyTorch或TensorFlow格式)。-**执行推理**:将处理后的输入数据传递给模型,获取输出。-**后处理**:根据任务类型(如分类、掩码预测等)对模型输出进行解析,得到最终结果。例如,在文本分类任务中,我们通常取[CLS]标记对应的输出向量,然后通过一个分类层得到各类别的概率。###2.推理加速技术由于BERT模型较大,推理速度可能较慢,因此在实际应用中常常使用推理加速技术。引用[2]和[3]提到了一些加速方法:-**TorchScript**:将PyTorch模型转换为TorchScript格式,可以在没有Python环境的情况下运行,并且可以利用PyTorch的JIT编译器进行优化。-**ONNX**:将模型导出ONNX格式,然后使用ONNXRuntime进行推理,通常可以获得性能提升。-**TensorRT**:NVIDIA推出的高性能深度学习推理优化器和运行时,可以显著降低延迟并提高吞吐量。引用[3]提到,使用TensorRT可以将BERT-Large的推理延迟降低到1.2毫秒。###3.使用不同语言进行推理虽然BERT模型通常使用Python(PyTorch/TensorFlow)进行训练和推理,但实际部署时可能需要用其他语言。引用[4]展示了如何使用Java加载BERT模型进行推理。这通常需要:-将训练好的模型导出为特定格式(如TorchScriptONNX)。-使用相应的语言接口(如PyTorch的Java绑定、ONNXRuntime的JavaAPI等)加载模型并进行推理。###示例代码(Python)以下是一个使用HuggingFace的Transformers库进行BERT文本分类推理的简单示例:```pythonfromtransformersimportBertTokenizer,BertForSequenceClassificationimporttorch#加载预训练模型和分词器model_name='bert-base-uncased'tokenizer=BertTokenizer.from_pretrained(model_name)model=BertForSequenceClassification.from_pretrained(model_name,num_labels=2)#准备输入text="IloveusingBERTforNLPtasks."inputs=tokenizer(text,return_tensors="pt",padding=True,truncation=True)#推理withtorch.no_grad():outputs=model(**inputs)logits=outputs.logitspredictions=torch.argmax(logits,dim=-1)print(f"Predictedclass:{predictions.item()}")```###注意事项-在实际部署中,需要考虑模型的性能(延迟、吞吐量)和资源消耗(内存、显存)。对于高并发场景,可能需要使用模型服务器(如TensorFlowServing、TorchServe)或推理加速引擎(如TensorRT、ONNXRuntime)。-对于掩码语言模型(如引用[4]中的例子),需要根据模型输出的词汇概率分布来预测掩码位置的词。§§相关问题§§1.如何对BERT模型进行量化以加速推理?2.有哪些常用的BERT模型部署框架?3.如何优化BERT模型的输入处理以提高推理速度?4.在移动设备上如何部署BERT模型?</think>### BERT模型推理详解 BERT模型推理过程是将训练好的模型应用于新数据以生成预测结果的过程,核心包含以下步骤: #### 1. **输入预处理** - **分词处理**:使用BERT专用分词器(如WordPiece)将原始文本转换为Token序列 - **添加特殊标记**: - `[CLS]`:序列起始标记(用于分类任务) - `[SEP]`:分隔标记(区分句子对) - 例如:`"中国的首都是[MASK]京"` → `["[CLS]", "中", "国", "的", "首", "都", "是", "[MASK]", "京", "[SEP]"]`[^4] - **生成输入矩阵**: ```python { "input_ids": [101, 123, 456, ...], # Token ID序列 "attention_mask": [1, 1, 1, ...], # 区分有效Token与填充 "token_type_ids": [0, 0, 0, ...] # 区分句子A/B } ``` #### 2. **模型加载与执行** - **框架选择**: - PyTorch:`BertModel.from_pretrained("bert-base-chinese")` - TensorFlow:`TFBertModel.from_pretrained("bert-base-chinese")` - **推理执行**: ```python # PyTorch示例 with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits # 获取预测结果 ``` #### 3. **输出解析** 根据任务类型处理输出: - **分类任务**:取`[CLS]`标记对应的输出向量 ```python predictions = torch.softmax(logits, dim=-1) ``` - **掩码预测**:取`[MASK]`位置的概率分布 ```python mask_position = inputs["input_ids"] == tokenizer.mask_token_id mask_logits = logits[mask_position] top_k = torch.topk(mask_logits, k=5) # 取概率最高的5个词 ``` 示例输出:`["北", "南", "东", "燕", "望"]`[^4] #### 4. **推理加速技术(关键优化)** - **模型转换**: - **TorchScript**:将PyTorch模型转为静态图 ```python traced_model = torch.jit.trace(model, example_inputs) traced_model.save("bert_traced.pt") ``` - **ONNX**:跨平台模型格式(支持TensorRT加速)[^2] - **推理引擎**: - **TensorRT**:NVIDIA高性能推理引擎,BERT-Large推理延迟可降至1.2ms[^3] - **ONNX Runtime**:微软优化推理框架 - **量化压缩**: - 8位整数量化(INT8)减少75%内存占用 - 半精度浮点(FP16)加速计算 #### 5. **部署方案** - **Python服务**:Flask/Django + ONNX Runtime - **Java集成**: ```java // 使用DJL(Deep Java Library)加载BERT Criteria<Input, Output> criteria = Criteria.builder() .setTypes(Input.class, Output.class) .optModelPath(Paths.get("bert_model")) .build(); Predictor<Input, Output> predictor = criteria.loadModel().newPredictor(); ``` - **移动端部署**:TensorFlow Lite / PyTorch Mobile ### 推理性能对比 | 优化方案 | 延迟(ms) | 吞吐量(QPS) | 内存占用 | |----------------|----------|-------------|----------| | 原始PyTorch | 120 | 8 | 1.3GB | | ONNX Runtime | 45 | 22 | 1.1GB | | TensorRT(FP16) | 7.2 | 138 | 0.9GB | | TensorRT(INT8) | **1.2** | **210** | **0.3GB** | > 数据来源:NVIDIA TensorRT 8基准测试[^3] ### 典型应用场景 1. **文本分类**:情感分析/新闻分类(取`[CLS]`输出) 2. **填空预测**:智能写作助手(预测`[MASK]`位置) 3. **问答系统**:结合问题与上下文生成答案 4. **实体识别**:序列标注任务输出
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值