Bert与TensorRT部署手册,享受丝滑的顺畅

DayNightStudy

共 6242字,需浏览 13分钟

 ·

2022-01-12 09:25

# 前言

1. 模型部署一直是我心头的一块刺,由于本人喜欢使用torch来做开发,可想而知并没像tf-serving这样强力的工具来帮助我进行快速简单的模型部署,不过笔者这个人就是比较认死理,虽然不好搞,但总得走出来一条路来

## 模型部署现有方法总结

1. 对于模型部署的方案目前比较流行的应该就几种:

flask(django,tornado)+genius+nginx的一套组合拳。

2. 近些年比较流行的FastAPI。

3. onnxruntime + Triton inference server。

4. TensorRT + Triton inference server

# Flask与FastAPI

1. 笔者大概就收集到了这些部署的方法,flask跟fastapi的方法比较简单,我想大部分人应该也比较熟悉这种方式,这里简单介绍一下,本质上是HTTP服务,当有请求的时候后端进行推理返回结果进行展示,这方面资料特别多,而且相对来说非常的简单,笔者就不重点介绍了

## ONNX与ONNXRuntime

1. onnx相信大部分torch的使用者对此应该比较熟悉它是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的框架可以采用相同格式存储模型数据并交互,而微软ONNXRuntime既是推理的一种工具,而我们的首要目标就是如何把torch保存的pth模型转换为onnx模型并利用onnxruntime进行运行,这里笔者也进行了一些调研,发现大部分都是在CV上的工作,而NLP上的特别少,不过幸运的是huggingface官方给出了转为onnx格式的一些方式(参考链接会在文末给出),不过huggingface给出的是一些pipline方法的,我们自己训练模型的话,还是需要自己动手,既然这样,那只能自己动手了

2. 首先我们先令保存的pth模型转换为onnx,通过以上代码就能把pth模型转换为onnx了,如此丝滑简单,而我们在进行推理的时候只需要利用onnxruntime进行加载推理即可,这里也给出代码:

torch.onnx.export(traced_model,                           args = (input_ids, attention_mask, token_type_ids),                           f=path,                           opset_version=13,                             do_constant_folding=True,                           verbose=False,                           input_names=['input_ids', 'attention_mask', 'token_type_ids'],                           output_names=['output'],                           dynamic_axes={"input_ids": {0: "batch_size", 1: "maxlen"},                                         "attention_mask": {0: "batch_size", 1: "maxlen"},                                         "token_type_ids": {0: "bacth_size", 1: "maxlen"},                                         }                           )

3. 这样我们就能通过onnx的方式对模型进行推理了,这样比torch本身的方式快上不少,但我们还能不能更快,答案是肯定的。

## TensorRT

1. Nvidia的TensorRT相信大部分人也都听说过了,这是Nvidia推出的一个高性能的深度学习推理框架,可以让深度学习模型在NVIDIA GPU上实现低延迟,高吞吐量的部署,再结合前一段看到了出的Torch-TensorRT能极大的简化转换的难度,笔者决定试试水。

2. 本来笔者心里已经做好了准备,但是其中的困难却比我想象的要复杂的多,因为没有root权限只能通过conda的环境进行安装,而想要安装Torch-TensorRT你需要最低CUDA10.2的要求,记住一定要把版本对齐,不然困难非常的大,而一般公司的电脑版本都比较稳定(落后)所以你还需要升级gcc跟g++的版本,如果没有cmake的话需要下载进行源码编译,而TensorRT要上到nvidia的官方网址对应好自己的版本进行源码编译安装,当然为了之后的考虑最好把pycuda也安装上,这其中也会报各种各样的错误,但都不太难解决,笔者这里选择了最新的TensorRT8.2跟cuda11.3的版本按照官网的要求安装好依赖,再次提醒版本一定要对,不然会出现很多问题,但是当笔者解决完这些问题的时候,准备跑过demo试一试,果然不出所料,直接报错,我心里有大大的问号??我明明跑的官方的demo为何有错,我定睛一看,原来是官方Blog中多了一个"]",到这里我对英伟达的官方BLOG基本已经失去信心了,不过在改掉这个小错误之后,跑成功了,内心狂喜,准备立马安排我的代码,果然又出问题了,在转换的过程中出现(Unsupported operator: aten::Int.Tensor(Tensor a) -> (int))到这笔者已经不知道该怎么办了,笔者在网上查找了一下,果然遇到此问题的人很多,而且并没有得到一个解决,看来这个库还需要完善,有时候想想可能这就是理想与现实的差距吧。

3. 既然Torch-TensorRT没法用,那该怎么办?我们来看看都有哪些方式转换为TensorRT

4. 硬怼,通过nvidia提供的api构建engine,并进行推理

5. 通过onnx2tensorrt把onnx转换为TensorRT

6. 利用Nvidia自带工具转换

7. Torch-TensorRT

8. Torch2trt

9. 首先第四种方法已经被我们淘汰了,解决不了,而硬怼对笔者来说还是稍微靠后一点吧,笔者这时候就是想找个简单的来试试,那么Torch2trt就是首选,他甚至不用转换为onnx跟Torch-TensorRT一样一键转换,但是鉴于之前的经验,觉得事情没那么简单,果然又是报错,到这里笔者已经没什么耐心去做调试了。

10. 笔者想着既然已经转换成onnx了,那么我们就退而求其次利用onnx2tensorrt来转换一下不就可以了吗,想着确实任务简单,但往往现实就是那么的残酷,笔者已经做好心理准备了,但是这次困难来的太快了,onnx2tensorrt需要利用Protobuf进行安装,这里笔者想着下的是最新版本的Tensorrt,而onnx2tensorrt的要求是Protobuf >= 3.0.x就行,那么笔者下载了最新的Protobuf-3.19版本,编译起来还是比较顺利的,然后按照TensorRT的编译要求进行编译,注意这点的配置一定要配好,比如protobuf的路径以及cuda的路径,当笔者把这一切都准备编译的的时候果然,又出问题了,glibc版本过低,那行笔者就对glibc进行升级,但是这有个问题,glibc属于底层库首先不太好绕开root其次如果glibc升级可能导致其他程序出问题,这么大的锅我可背不起,最后实在是没办法了,只能申请运维把docker权限放给我,利用docker来构建,这下就绕开了glibc的问题,笔者通过nvidia官方下载了Tensorrt的docker,运行起来后配置了一大堆东西,然后按照刚刚的步骤重新来到onnx-tensorRT这里,这次glibc没有报错,但protobuf报错了,说实话到这里笔者心态已经有点爆炸了,这方面的资料真的是少之又少,只能自己硬着头皮搞,那么既然报错,就从错误信息找,笔者找到了报错的原因是因为

coded_input.SetTotalBytesLimit(std::numeric_limits::max(), std::numeric_limits::max() / 4)

这个行代码报错,笔者通过提示寻到了protobuf里而在protobuf里这行代码已经变成了只有一个参数值了,我真的是无语了,但笔者现在该选择什么版本呢,什么时候改的我并不清楚,只能从git上下下来,然后查看历史版本,果然前几个月改的,那么我现在有两个选择第一下载前一个版本,第二修改这行代码,没办法笔者这个人喜欢新的,那就只能改源码了,笔者把cpp跟hpp文件中的代码改成了单参数版本,这样配置好参数make && make install之后成功安装,准备试试转换如何,但是正当笔者尝试转换的时候果然又报错了,说实话到这笔者有点想打人了,我反思下自己,没事瞎搞什么,但笔者不甘心,剩下的只有两种方法,自带工具转换已经硬怼。

11. 这里笔者有点想硬怼了,没办法了,但是对自带工具还是报了一些希望,打算先试一试吧,这一试竟然成功了,到这里笔者非常的高兴,终于转成功,但是推理的代码还需要自己写,没办法,硬来吧,这里笔者参考了torch2trt中推理的一些代码.

class TRTModule(torch.nn.Module):    def __init__(self, engine=None, input_names=None, output_names=None):        super(TRTModule, self).__init__()        # self._register_state_dict_hook(TRTModule._on_state_dict)        self.engine = engine        if self.engine is not None:            self.context = self.engine.create_execution_context()        self.input_names = input_names        self.output_names = output_names    def forward(self, *inputs):        batch_size = inputs[0].shape[0]        bindings = [None] * (len(self.input_names) + len(self.output_names))
# create output tensors outputs = [None] * len(self.output_names) for i, output_name in enumerate(self.output_names): idx = self.engine.get_binding_index(output_name) # c = inputs[i].shape dtype = torch_dtype_from_trt(self.engine.get_binding_dtype(idx)) shape = tuple(self.engine.get_binding_shape(idx)) device = torch_device_from_trt(self.engine.get_location(idx)) output = torch.empty(size=shape, dtype=dtype, device=device) outputs[i] = output bindings[idx] = output.data_ptr()
for i, input_name in enumerate(self.input_names): idx = self.engine.get_binding_index(input_name) self.context.set_binding_shape(idx, tuple(inputs[i].shape)) bindings[idx] = inputs[i].contiguous().data_ptr()
self.context.execute_async( batch_size, bindings, torch.cuda.current_stream().cuda_stream )
outputs = tuple(outputs) if len(outputs) == 1: outputs = outputs[0]
return outputs

1. 正准备推理时,果然,又报错了,哎~说实话已经有些习惯了,就像被老板反复按在地上摩擦,但你又不得不干的时候,习惯真是可怕的力量,有问题就只能解决,原来我的onnx中运用了dynamic_axes动态输入的参数,那么现在有两种方案来解决,第一.指定为静态shape,第二.TensorRT转换为动态输入,都已经到这了,笔者当然会选择动态输入了,但是要怎么做,只能看官方文档,文章中指出需要指定动态输入的三个参数minShapes,optShapes与maxShapes,当你指定好之后顺利转换成功,然后运行刚刚写好的推理代码,哈哈~,再次失败。。不过这次的问题很好解决就是笔者的torch的cuda版本不对,当与cuda11.3对齐过后顺利得到了结果。

2. 到这里笔者搞了将近一个月的Tensort算是完成了,剩下的就是把Tensorrt利用Triton inference server进行推理,以及一些推理过程的比较,这个下一篇文章再写吧,哎,这一篇一篇文章最后都留下了一些坑没填,本想填坑,但业务需求一直在变,也没腾出手来做一些事情,等到一部分事情做完的时候,就慢慢的把坑填起来,忽然想起笔者这里还有一篇之前做的关于Prompt的实验还没有写文章,这个就准备准备也分享一下了~



浏览 113
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报