1背景

随着CV算法在业务场景中应用越来越多,给咱们带来了新的挑战,须要晋升Python推理服务的性能以降低生产环境老本。为此咱们深刻去钻研Python GPU推理服务的工作原理,推理模型优化的办法。最终通过两项要害的技术: 1.Python的GPU与CPU过程拆散,2.应用TensorRT对模型进行减速,使得线上大部分模型服务QPS晋升5-10倍左右,大量节约了线上GPU推理服务的老本。

针对下面的两项关键技术,咱们还自研了相干框架与工具进行积淀。包含基于Python的CPU与GPU过程主动隔离的推理服务框架,以及对推理模型进行转TensorRT优化的调试工具。

此外针对不同的推理服务性能瓶颈,咱们还梳理了各种实战优化技巧,比方CPU与GPU拆散,TensorRT开启半精度优化,同模型混合部署,GPU数据传输与推理并行等。

上面从实践,框架与工具,实战优化技巧三个方面介绍下推理服务性能优化的办法。

2实践篇

2.1 CUDA架构

CUDA 是 NVIDIA 创造的一种并行计算平台和编程模型。它通过利用图形处理器 (GPU) 的解决能力,可大幅晋升计算性能。

CUDA的架构中引入了主机端(host, cpu)和设施(device, gpu)的概念。CUDA的Kernel函数既能够运行在主机端,也能够运行在设施端。同时主机端与设施端之间能够进行数据拷贝。

CUDA Kernel函数:是数据并行处理函数(核函数),在GPU上执行时,一个Kernel对应一个Grid,基于GPU逻辑架构散发成泛滥thread去并行执行。
CUDA Stream流:Cuda stream是指一堆异步的cuda操作,他们依照host代码调用的程序执行在device上。

典型的CUDA代码执行流程:
a.将数据从Host端copy到Device端。
b.在Device上执行kernel。
c.将后果从Device段copy到Host端。

以上流程也是模型在GPU推理的过程。在执行的过程中还须要绑定CUDA Stream,以流的模式执行。

2.2 传统Python推理服务瓶颈

2.2.1 传统Python推理服务架构

因为Python在神经网络训练与推理畛域提供了丰盛的库反对,加上Python语言本身的便利性,所以推理服务大多用Python实现。CV算法的推理引擎大多采纳Python flask框架或Kserve的框架间接实现。这种框架大抵调用流程如下:

以上架构是传统推理服务的罕用架构。这种架构的劣势是代码写起来比拟通俗易懂。然而在性能上有很大的弊病,所能承载的QPS比拟低。咱们用了几个CV模型去压测,极限QPS也个别不会超过4。

2.2.2 瓶颈剖析

因为以上架构的CPU逻辑(图片的前解决,后处理)与GPU逻辑(模型推理)在同一个线程内,所以会存在如下性能瓶颈:

  • 如果是单线程的模式,CPU逻辑与GPU逻辑互相期待,GPU Kernel函数调度有余,导致GPU使用率不高。无奈充沛晋升QPS。这种状况下只能开启更多过程来晋升QPS,然而更多过程会带来更多显存的开销。
  • 如果开启多线程模式,通过实测,这种形式也不能带来QPS的晋升。次要是因为Python的GIL锁的起因,因为Python GIL锁的存在,Python的多线程实际上是伪的多线程,并不是真正的并发执行,而是多个线程通过争抢GIL锁来执行,这种状况下GPU Kernel launch线程不能失去充沛的调度。在Python推理服务中,开启多线程反而会导致GPU Kernel launch线程频繁被CPU的线程打断。因为GPU kernel lanch调度有余,这种形式也无奈充分利用GPU使用率。

    2.2.3 解决方案

    针对以上问题,咱们的解决方案是把CPU逻辑与GPU逻辑拆散在两个不同的过程中。CPU过程次要负责图片的前解决与后处理,GPU逻辑则次要负责执行cuda kernel 函数,即模型推理。

另外因为咱们线上有大量推理服务在运行,所以咱们基于Python开发了一个CPU与GPU拆散的对立框架。针对原有Flask或Kserve的服务,稍作批改即可应用咱们的服务。具体请参考上面的CPU与GPU拆散的对立推理框架相干介绍。

针对线上的某个推理服务,应用咱们的框架进行了CPU与GPU过程拆散,压测得出的数据如下,可见QPS大概晋升了7倍左右。

2.3 TensorRT模型减速原理

TensorRT是由英伟达公司推出的一款用于高性能深度学习模型推理的软件开发工具包,能够把通过优化后的深度学习模型构建成推理引擎部署在理论的生产环境中。TensorRT提供基于硬件级别的推理引擎性能优化。
下图为业界最罕用的TensorRT优化流程,也是以后模型优化的最佳实际,即pytorch或tensorflow等模型转成onnx格局,而后onnx格局转成TensorRT进行优化。

其中TensorRT所做的工作次要在两个期间,一个是网络构建期,另外一个是模型运行期。

a.网络构建期
i.模型解析与建设,加载onnx网络模型。
ii.计算图优化,包含横向算子交融,或纵向算子交融等。
iii.节点打消,去除无用的节点。
iv.多精度反对,反对FP32/FP16/int8等精度。
v.基于特定硬件的相干优化。

b.模型运行期
i.序列化,加载RensorRT模型文件。
ii.提供运行时的环境,包含对象生命周期治理,内存显存治理等。

以下是咱们基于 VisualTransformer模型进行的TensorRT优化前后的性能评测报告:

3框架与工具篇

这一篇章,次要介绍咱们本人推出的框架与工具。其中框架为CPU与GPU拆散的Python对立推理框架,工具则为Onnx转TensorRT的半自动化调试工具。相干框架与工具咱们在线上大量推理服务推动应用中。

其中CPU与GPU拆散的Python对立推理框架解决了一般Python推理服务无奈主动隔离CPU与GPU的问题,用户只须要继承并实现框架提供的前解决,推理,后处理相干接口,底层逻辑即可主动把CPU与GPU进行过程级别隔离。

其中TensorRT半自动化调试工具,次要定位并解决模型转TensorRT的过程中遇到的各种精度失落问题。底层基于TensorRT的相干接口与工具进行封装开发。简化TensorRT的优化参数。

3.1 CPU与GPU拆散的对立推理框架

新架构设计计划如下:

方案设计的思路是GPU逻辑与CPU逻辑拆散到两个过程,其中CPU过程次要负责CPU相干的业务逻辑,GPU过程主负责GPU相干推理逻辑。同时拉起一个Proxy过程做路由转发。

(1)Proxy过程
Proxy过程是零碎门面,对外提供调用接口,次要负责路由散发与健康检查。当Proxy过程收到申请后,会轮询调用CPU过程,散发申请给CPU过程。

(2)CPU过程
CPU过程次要负责推理服务中的CPU相干逻辑,包含前解决与后处理。前解决个别为图片解码,图片转换。后处理个别为推理后果断定等逻辑。
CPU过程在前解决完结后,会调用GPU过程进行推理,而后持续进行后处理相干逻辑。CPU过程与GPU过程通过共享内存或网络进行通信。共享内存能够缩小图片的网络传输。

(3)GPU过程
GPU过程次要负责运行GPU推理相干的逻辑,它启动的时候会加载很多模型到显存,而后收到CPU过程的推理申请后,间接触发kernel lanuch调用模型进行推理。

该计划对算法同学提供了一个Model类接口,算法同学不须要关怀前面的调用逻辑,只须要填充其中的前解决,后处理的业务逻辑,既可疾速上线模型服务,主动拉起这些过程。

该计划把CPU逻辑(图片解码,图片后处理等)与GPU逻辑(模型推理)拆散到两个不同的过程中。能够解决Python GIL锁带来的GPU Kernel launch调度问题。

3.2 TensorRT调试工具

TensorRT尽管不是齐全开源的,然而官网给出了一些接口与工具,基于这些接口与工具咱们能够对模型优化流程进行剖析与干涉。基于TensorRT官网提供的接口与工具,咱们本人研发了一套工具。用户能够应用咱们的工具把模型转成TensorRT格局,如果在模型转换的过程中呈现精度失落等问题,也能够应用该工具进行问题定位与解决。

自研工具次要在两个阶段为用户提供帮忙,一个阶段是问题定位,另一个阶段是模型转换。具体形容如下:

3.2.1 问题定位

问题定位阶段次要是为了解决模型转TensorRT开启FP16模式时呈现的精度失落问题。个别分类模型,对精度的要求不是极致的状况下,尽量开启FP16,FP16模式下,NVIDIA对于FP16有专门的Tensor Cores能够进行矩阵运算,相比FP32来说吞吐量晋升一倍以上。
比方在转TensorRT时,开启FP16呈现了精度失落问题,自研工具在问题定位阶段的大抵工作流程如下:

次要工作流程为:
(1)设定模型转换精度要求后,标记所有算子为输入,而后比照所有算子的输出精度。
(2)找到最早的不合乎精度要求的算子,对该算子进行如下几种形式干涉。

  • 标记该算子为FP32。
  • 标记其父类算子为FP32。
  • 更改该算子的优化策略(具体参考TensorRT的tactic)

循环通过以上两个步骤,最终找到合乎指标精度要求的模型参数。这些参数比方,须要额定开启FP32的那些算子等。而后相干参数会输入到配置文件中,如下:

3.2.2 模型转换

模型转换阶段则间接应用下面问题定位阶段失去的参数,调用TensorRT相干接口与工具进行转换。
此外,咱们在模型转换阶段,针对TensorRT原有参数与API过于简单的问题也做了一些封装,提供了更为简洁的接口,比方工具能够主动解析ONNX,判断模型的输出与输入shape,不须要用户再提供相干shape信息了。

4优化技巧实战篇

在理论利用中,咱们冀望用户可能对一个推理模型开启CPU与GPU拆散的同时,也开启TensorRT优化。这样往往能够失去QPS两次优化的叠加成果。比方咱们针对线下某个分类模型进行优化,应用的是CPU与GPU拆散,TensorRT优化,并开启FP16半精度,最终失去了10倍的QPS晋升。

以下是咱们在模型优化过程中的一些实战技巧,梳理一下,分享给大家。

(1)分类模型,CPU与GPU拆散,TensorRT优化,并开启FP16,失去10倍QPS晋升
某个线上基于Resnet的分类模型,对精度损失能够承受误差在0.001(误差定义:median,atol,rtol)范畴内。因而咱们对该推理服务进行了三项性能优化:
a.应用咱们提供的GPU与CPU拆散的对立框架进行革新。
b.对模型转ONNX后,转TensorRT。
c.开启FP16模式,并应用自研工具定位到两头呈现精度损失的算子,把这些算子标记为FP32.

通过以上优化,最终失去了10倍QPS的晋升(与原来Pytorch直接推理比拟),老本上失去比拟大的缩减。

(2)检测模型,CPU与GPU拆散,TensorRT模型优化,QPS晋升4-5倍左右。
某个线上基于Yolo的查看模型,因为对精度要求比拟高,所以没有方法开启FP16,咱们间接在FP32的模式下进行了TensorRT优化,并应用对立框架进行GPU与CPU拆散,最终失去QPS 4-5倍的晋升。

(3)同模型反复部署,充分利用GPU算力资源
在理论的场景中,往往GPU的算力是短缺的,而GPU显存是不够的。通过TensorRT优化后,模型运行时须要的显存大小个别会升高到原来的1/3到1/2。

为了充分利用GPU算力,框架进一步优化,反对能够把GPU过程在一个容器内复制多份,这种架构即保障了CPU能够提供短缺的申请给GPU,也保障了GPU算力充分利用。优化后的架构如下图:

5总结

采纳以上两个推理模型的减速技巧,即CPU与GPU过程隔离,TensorRT模型减速。咱们对线上的大量的GPU推理服务进行了优化,也节俭了比拟多的GPU服务器老本。
其中CPU与GPU过程隔离次要是针对Python推理服务的优化,因为在C++的推理服务中,不存在Python GIL锁,也就不存在Python Kernel launch线程的调度问题。目前业界开源的Python推理服务框架中,还没有提供相似的优化性能,所以咱们后续有思考把Python对立推理服务框架进行开源,心愿能为社区做一点奉献。
此外TensorRT的模型优化,咱们参考了大量NIVIDIA的官网文档,在下层做了封装,后续会进一步深入研究。