UC CV类模型优化总结

简介: CV模型是业务中常见的模型,但是我们观察到UC集群中的CV类模型还有很大的GPU利用率提升空间。如果不对此进行优化,则需要大量的GPU资源才能才能满足延迟要求。本文主要介绍了CV类模型的优化总结。

背景

CV 模型是业务中常见的模型,但是我们观察到 UC 集群中的 CV 类模型还有很大的 GPU 利用率提升空间。如果不对此进行优化,则需要大量的 GPU 资源才能才能满足延迟要求。为了提高资源利用率、节省计算资源,我们对这部分模型(主要是 PyTorch)的优化进行了一些探索,并针对使用资源较多的 Detectron2 模型进行测试。


Detectron2采用centernet作为backbone 提供特征提取能力,提取的特征经过proposal_generator 生成候选框并对候选框进行调整,排重和初步过滤,最后由 roi_heads 进行打分排序输出目标检测的score 和boxes 。


模型计算耗时主要集中在backbone部分。线上采用Python直接部署,优化前单卡服务能力较弱,单卡超过12qps的时候开始出现排队情况,并且在qps 20 后 延迟急剧放大甚至服务异常。为了维持服务稳定运行,线上只能使用较多的计算资源来提升qps,请求高峰期时,服务的qps在1850左右。 但这种以增加资源的方式来提升qps的做法会造成大量的资源浪费,经观察,在高峰期gpu的使用率只能维持在20%左右。

优化方案探索

优化主要考虑两个方向:1)提升单 CUDA Stream 条件下模型的推理速度;2)利用多CUDA Stream进一步提升GPU利用率。

模型推理后端优化

优化后端主要关注点

作为工程团队,我们比较关注优化工具的如下特性:

  • 优化的成功率:PyTorch具有灵活、扩展性强、OP种类丰富等特点,这会导致其建模、推理代码的多样性很高。而另一方面,多数后端支持的算子类型仅为PyTorch的一个子集。如何在这种情况化最大化优化的成功率是最需要考量的一点。
  • 优化的性能:一方面,可以充分发挥部署硬件的能力,降低用户响应时间,同时节省成本;另一方面,可以在保持响应时间不变的前提下,使用结构更为复杂的深度学习模型,进而提升业务精度指标。
  • 优化及部署难易程度:优化工具需要提供简洁、稳定、易于使用的API,能够使得其使用者快速地完成模型优化的流程。同时,优化后的模型需要能够较容易地部署至现有工程体系中。

几类主流的优化手段

原生TensorRT

从优化的性能角度而言,CV领域模型当前的最优做法是全图转TensorRT,以充分利用其加速特性,团队内也已沉淀了许多TensorRT的加速经验并累积了一些TensorRT插件。要将PyTorch模型转化至TensorRT,有两种主流的方式:

1、PyTorch模型导出ONNX,再由onnx2trt将其解析成TensorRT的网络;

2、直接调用TensorRT network的API构建其等价模型,并加载参数。

上述方式都存在的问题是:TensorRT支持的算子种类的少于ONNX,同时PyTorch与ONNX之间也存在许多算子无法转化。因此这两种方式的优化成功率会较低。此外,TensorRT某些Pluginin的实现还存在鲁棒性问题,在不同模型上可能会存在数值差异(比如roiAlign)。

TensorRT-based Backend

考虑到PyTorch算子的多样性,我们进一步尝试基于子图架构的TensorRT-based backend。子图是指在优化前的计算图中进行可优化OP的选择,同时将这些聚合成子图,每个子图会被转化至相应的后端进行优化。而无法转化至相应后端的op,则回退至PyTorch执行。


这种架构可以在保证部署成功率的前提下获得最大的优化效果。目前社区中主流的以圈图架构接入TensorRT的优化工具包括两款,即Torch-TensorRT以及PAI-Blade。

图转化架构上而言,Torch-TensorRT支持TorchScript以及Torch Fx Module两种输入。它会针对PyTorch OP编写许多Converter,在Converter中调用TensorRT netowrk的API进行OP翻译并构建网络。PAI-Blade支持TorchScipt作为输入,对于子图TorchScript会经过ONNX并转化为TensorRT network。经由ONNX的路线可以复用社区成熟的工具,但多一层中间表示会使得定制化开发的链路较长;直接写converter可定制化链路缩短,但开发量大大提升,需要针对每个OP以及其不同的状况从头开发。当前阶段而言,经由ONNX路线的成熟度是更高的。

TensorRT功能覆盖度而言,二者皆提供了dynamic range、低精度优化、自定义plugin等功能。然而,Torch-Tensor如果需要启用dynamic range、int8量化等高阶功能,需要输入的模型能够做到全网转化到TensorRT,而PAI-Blade则在支持在圈图的架构下,每个子图单独启用这些高阶功能。

优化成功率以及优化性能方面,PAI-Blade针对TorchScript的特性开发了大量的JIT passes使得其优化后的图更为干净高效,圈图的范围也更大,进而可以获得更大的优化效果。但是由于ONNX这一层中间媒介的存在,使得一些冷门算子无法被转化至TensorRT后端。而Torch-TensorRT当前阶段的成熟度较低,优化成功率也较低。

除去TensorRT外,我们还调研了OneFlow、PaddlePaddle等业界比较流行的后端,这些后端都存在着和原生TensorRT 类似的问题,如算子支持不完整、opset 比较低等问题。


经过一段时间的对比,综合各方面考虑,最终选择易用性、鲁棒性和性能更好的 PAI-Blade 优化方案, 并对原始模型实现进行重写和改图适配。

多 CUDA Stream 优化

PyTorch 模型将所有线程的cuda 操作放到default stream, 导致多线程跑的时候 在CUDA kernel执行上串行化,而且还阻塞其他stream的执行,多线程内部执行的推理都是相互独立的个体,执行没有上下有依赖,因此可以对stream打散处理。一个典型的单CUDA Stream的timeline如下:

优化过程

模型推理代码重写

PyTorch模型在nn.Module层面是不包含一张“显式”的计算图的,需要将其转化成为TorchScript以获得推理计算图,进而进行推理优化。一方面,TorchScript仅为Python的一个子集,支持的语法受限。另一方面,PyTorch动态图的特性会导致模型的推理代码多样性非常大。这个冲突会导致在不修改推理代码的前提下,无法导出TorchScript。因此我们需要对Detectron2推理代码做出修改,主要包含如下几个部分:

语法和逻辑重写

      1. 变量、参数显式增加类型标注

            torchscript 会将没有显式标注的参数类型当做Tensor,如果实际变量未标注,同时类型不为Tensor的话,推理的过程出现类型不匹配的异常。

# 标注前
def forward(self, x, dim):
    return torch.sum(x, dim=dim)
# 标注后
def forward(self, x: torch.Tensor, dim: int):
    return torch.sum(x, dim=dim)

      2. 诸如Dict、List等容器类型的存储内容保持一致

TorchScript要求容器对象内存储的数据类型完全一致,比如一个List中所有数据的类型需要保持一致。

# 修改前
x = [1, "str"]
num_data = x[0]
str_data = x[1]
# 修改后
int_x = [1,]
str_x = ["str",]
num_data = int_x[0]
str_data = str_x[0]

     3.  对于__getattr__等TorchScript不支持的magic method进行重写

def init():
      self.local_modules :nn.ModuleList = []
      self.local_modules_tmp :Dict[str, nn.Module] = {}
      #self.add_module("acb", lateral_conv)
      self.local_modules_tmp["acb"] = lateral_conv.cuda()
      self.local_modules = nn.ModuleDict(self.local_modules_tmp)
def forward(self, input_node):
     for kn, vv in self.local_modules.items():
         if name == kn:
              input_node = vv(input_node)
     #input_node = self.__getattr__(name)(input_node)

     4. * 展开功能替换

boxlists = list(zip(*sampled_boxes)) # 替换为for循环

     5. list、dict、NoneType 等类型修改。list替换为 torch.nn.ModuleList,dict 替换为 torch.nn.ModuleDict等。

     6. 善用bypass的机制来忽略推理无关的代码分支

模型的推理代码可能同时存在着训练和推理相关的部分,同时还会包括前后处理等不属于模型推理本身的内容。这部分代码可以直接通过相关机制进行bypass。

# 修改前
if self.traininig:
    do_some_training_thing
else:
    do_some_eval_thing
# 修改后
if self.traininig:
    assert not torch.jit.is_scripting(), "Bypass the codes blow"
    do_some_training_thing  # assert之后的代码均不会被翻译
else:
    do_some_eval_thing

     7.  目前部署环境目前都用单卡推理,可以使用 torch.nn.BatchNorm2d 代替 torch.nn.SyncBatchNorm。

模型参数的key修改

  在模型代码修改完毕后,由于torch.nn.Module的组织形式会发生一些变化,会使得其在state_dict中的key发生变化,由于存在key mismatch的情况,无法直接加载预训练模型。因此我们需要修改预训练模型的key以达到正确加载的目的。

self.linear = xxx  # key : “linear”
self.linear = torch.nn.ModuleList([xxx])  # key: "linear.0","linear.1",..

 拆图

实际测试中,并不是全图优化后的端到端性能是最好的,此外GPU运行的性能不一定由于CPU。因此,我们针对不同的网络组建(backbone, neck,  detection head)以及不同的代码逻辑进行了拆图,每个部分采取各自更优的方式进行优化。

转换&圈图分析

PAI-Blade优化

import torch_blade
import torch_blade.tensorrt
model_path = "./opt_model_backbone.pt"
# 加载优化前TorchScript
script_model = torch.jit.load(model_path)
script_model.cuda().eval()
...
# 配置优化
cfg = torch_blade.Config()
cfg.optimization_pipeline = "TensorRT" # 采取TensorRT后端
# 执行优化 
with cfg: 
  opt_model = torch_blade.optimize(script_model, False, model_inputs=img)
    print("Optimize finish!")
  # 优化后的模型同样是TorchScript,可以直接调用相关api序列化到本地
    torch.jit.save(opt_model, opt_file_name)
    print("save finish!")
# 加载模型并推理
opt_model = torch.jit.load(opt_file_name)
# 执行推理
opt_model(img)

 圈图分析

PAI-Blade圈图默认是最少三个基础的op,对于费时的op如果没有圈入到trt-grp执行,可以手工设置白名单,然圈图优化对该OP进行特殊处理,另外如果优化后性能不及预期,可以通过圈图信息分析查看是否存在关键子图未被圈入trt-grp

多CUDA Stream    

优化结果

两项优化在 Detectron2 模型上线之后,我们看到系统的 QPS 和 GPU 利用率均有了大幅提升。优化后可以节省接近 65% 的计算资源。

Latency(ms)

QPS

GPU-util

优化前

110

17

30%

优化后

90

53(+200% )

70%

优化前


优化后


此外,我们也评估了 PAI-Blade 在其他典型模型上的优化效果:

模型结构

优化前(延迟 100次平均)

优化后(延迟 100次平均)

resnet18

2.958

0.878 (200%+)

mobilenet-v2

2.324

0.746 (200%+)

yolox

9.174

3.883(150%+)

mmdet+swin transformer tiny

48

28(70%+)

随着优化在其他模型上的逐渐上线,预计将带来整体160+张GPU的优化收益。

总结

作为 UC 的模型推理优化团队,在不影响模型业务效果的情况下提高模型推理性能和减少计算资源使用是我们的主要目标。 在这次优化中,我们从优化的成功率、优化的性能、优化及部署难易程度三个角度对优化工具进行选型,最终选择了 PAI-Blade 集成到系统中。从最终的性能优化效果及资源成本的节省来看,基本达到了最初的预期。但性能优化没有终点,随着模型结构的不断进化、模型的规模逐渐变大,新的挑战还会不断出现,但优化工具的成功率、绝对性能、易用性仍然是我们技术选型的核心标准。

相关实践学习
机器学习概览及常见算法
机器学习(Machine Learning, ML)是人工智能的核心,专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能,它是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域。 本课程将带你入门机器学习,掌握机器学习的概念和常用的算法。
相关文章
|
9天前
|
人工智能 自然语言处理 API
最近全网爆火的Jev到底是什么?系统一模型原理与使用教程
综合来看,Jev给AI工程带来了新的分层思路,复杂生成任务交给传统大模型,高频简单判断交给Jev,二者组合搭建分层智能架构,兼顾输出能力、响应速度与调用成本。对于做客服系统、Agent智能体、内容审核、批量数据打标签、工单路由的开发者,值得做POC验证;但如果业务以文本生成、深度推理为主,Jev增益有限。接入前建议拿真实业务数据跑对比测试,评估准确率与误判率,同时设置合理概率阈值并配置人工兜底流程,才能充分发挥这套决策模型的价值,构建更稳定高效的AI业务系统。
206 2
|
机器学习/深度学习 监控
数据漂移、概念漂移以及如何监控它们(mona)
在机器学习模型监控的上下文中经常提到数据和概念漂移,但它们到底是什么以及如何检测到它们?此外,考虑到围绕它们的常见误解,是不惜一切代价避免数据和概念漂移的事情,还是在生产中训练模型的自然和可接受的后果?请仔细阅读,找出答案。在本文中,我们将提供模型漂移的细粒度细分,以及检测它们的方法以及处理它们时的最佳实践。
|
JavaScript Java 测试技术
『App自动化测试之Appium基础篇』| 从定义、原理、环境搭建、安装问题排查等深入了解Appium
『App自动化测试之Appium基础篇』| 从定义、原理、环境搭建、安装问题排查等深入了解Appium
5766 0
|
11月前
|
新制造
2025融资租赁公司排名与推荐:深度解析海尔融资租赁的产业金融路径
在高质量发展背景下,融资租赁业正深度融入实体经济。本文基于公开数据与多维指标,系统梳理2025年主流企业,聚焦居首的海尔融资租赁。其以“为产业,而金融”为核心,深耕智能制造、医疗健康等四大领域,累计服务超9000家企业,投入超1200亿元。公司创新“金融+设备+服务”模式,打造“海融云服”数字化平台,2024年投放额达188亿元,不良率低于1%。积极践行科技、绿色、普惠金融,服务“专精特新”与小微企业成效显著。获AAA评级及多项行业荣誉,展现卓越风控与社会责任。其产融结合路径为行业提供转型范本,助力构建可持续金融生态。
603 1
|
JavaScript 数据安全/隐私保护
Discuz! X3.5 注册页面提示语的修改方法
Discuz! X3.5 注册页面提示语的修改方法
1161 2
|
Java 关系型数据库 MySQL
基于springboot的大学生家教管理系统
本文介绍了家教管理系统的开发背景与意义,分析了系统开发所需的技术与环境,包括MySQL数据库、Java语言、SpringBoot框架及B/S模式框架。系统设计与实现部分展示了整体架构及功能模块,旨在提升家教信息管理的效率与规范性,实现数据处理的高效与稳定。
|
算法
三电平逆变器SVPWM控制(无解耦功能)与谐波分析
三电平逆变器的空间矢量脉宽调制(SVPWM)控制方法,重点分析在不使用解耦控制的情况下实现5%谐波含量的技术方案。我们将使用MATLAB/Simulink进行建模和仿真分析。
858 1
|
人工智能 算法 安全
人工智能伦理与监管:构建负责任的AI未来
【10月更文挑战第3天】随着人工智能(AI)技术的快速发展,其在社会各领域的应用日益广泛。然而,AI的广泛应用也带来了一系列伦理和监管挑战。本文旨在探讨AI的伦理问题,分析现有的监管框架,并提出构建负责任AI未来的建议。同时,本文将提供代码示例,展示如何在实践中应用这些原则。
2317 1
|
机器学习/深度学习 数据采集 算法
机器学习实战第1天:鸢尾花分类任务
机器学习实战第1天:鸢尾花分类任务