共计 5035 个字符,预计需要花费 13 分钟才能阅读完成。
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 的官网文档,在下层做了封装,后续会进一步深入研究。