1. KubeAI介绍

KubeAI是得物AI平台,是咱们在容器化过程中,逐渐收集和开掘公司各业务域在AI模型钻研和生产迭代过程中的需要,逐渐建设而成的一个云原生AI平台。KubeAI以模型为主线提供了从模型开发,到模型训练,再到推理(模型)服务治理,以及模型版本继续迭代的整个生命周期内的解决方案。

在数据方面,KubeAI提供基于cvat的标注工具,与数据处理及模型训练流程买通,助力线上模型疾速迭代;提供工作/Pipeline编排性能,对接ODPS/NAS/CPFS/OSS数据源,为用户提供一站式AI工作站。平台自研推理引擎助力业务在进步模型服务性能的同时还能管制老本;自研训练引擎进步了模型训练任务吞吐量,缩短了模型的训练时长,帮忙模型开发者减速模型迭代。

此外,随着AIGC的炽热倒退,咱们通过调研公司外部AI辅助生产相干需要,上线了AI制图性能,为得物海报、营销流动、设计师团队等业务场景提供了根底能力和通用AI制图能力。

此前,咱们通过一文读懂得物云原生AI平台-KubeAI的落地实际过程一文,向大家介绍了KubeAI的建设和在业务中的落地过程。本文,咱们将重点介绍下KubeAI平台在推理、训练和模型迭代过程中的外围引擎能力实践经验。****

2.AI推理引擎设计实现

2.1 推理服务现状及性能瓶颈剖析

Python语言以其灵便轻捷的特点,以及其在神经网络训练与推理畛域提供了丰盛的库反对,在模型钻研和开发畛域被宽泛应用,所以模型推理服务也次要以Python GPU推理为主。模型推理过程个别波及预处理、模型推理、后处理过程,单体过程的形式下CPU前/后处理过程,与GPU推理过程须要串行,或者假并行的形式进行工作,大抵流程如下图所示:

上述架构的劣势是代码写起来比拟通俗易懂,但在性能上有很大的弊病,所能承载的QPS比拟低。通过在CV域的模型上进行压测,咱们发现推理QPS很难达到5,深入分析发现造成这一问题的起因如下:

(1)单线程模式下,CPU逻辑与GPU逻辑互相期待,GPU Kernel函数调度有余,导致GPU使用率不高,无奈充沛晋升服务QPS。这种状况下只能开启更多过程来晋升QPS,然而更多过程会带来更大的GPU显存开销。

(2)多线程模式下,因为Python的GIL锁的起因,Python的多线程实际上是伪的多线程,并不是真正的并发执行,而是多个线程通过争抢GIL锁来执行,这种状况下GPU Kernel Launch线程不能失去充沛的调度。此外,在Python推理服务中开启多线程反而会导致GPU Kernel Launch线程频繁被CPU的线程打断,所以GPU算力也会始终“朝气蓬勃”,继续低下。

以上问题使得 如果推理服务想要撑持更多的流量,只能做横向的减少服务实例数,随同着老本的上涨。

2.2 自研推理服务对立框架kubeai-inference-framework

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

为了不便模型开发者更疾速地接入咱们的优化计划,咱们基于Python开发了一个CPU与GPU过程拆散的对立框架***kubeai-inference-framework***,旧有Flask或Kserve的服务,稍作批改即可接入推理引擎对立框架,新增服务依照框架实现指定function即可。推理服务对立框架构如下图所示:

如前所述,推理服务对立框架的次要思路是把GPU逻辑与CPU逻辑拆散到两个过程,除此之外,还会拉起一个Proxy过程做路由转发。

CPU过程

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

GPU过程

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

\_kubeai-inference-framework\_框架中对模型开发者提供了一个\_Model\_类接口,他们不须要关怀前面的调用逻辑,只须要填充其中的前解决,后处理的业务逻辑,就能够疾速上线模型服务,主动拉起这些过程。

Proxy过程

Proxy过程是推理服务入口,对外提供调用接口,负责路由散发与健康检查。当Proxy过程收到申请后,会轮询调用CPU过程,散发申请给CPU过程进行解决。

自研的推理服务对立框架,把CPU逻辑(图片解码,图片后处理等)与GPU逻辑(模型推理)拆散到两个不同的过程中后,无效解决了Python GIL锁带来的GPU Kernel Launch调度问题,晋升了GPU利用率,进步了推理服务性能。针对线上的某个推理服务,应用咱们的框架进行了CPU与GPU过程拆散,压测得出的数据如下表所示,能够看到QPS晋升了近7倍。

推理服务框架类型QPS耗时(s)GPU算力使用率(%)
传统多线程架构4.51.052.0
自研推理服务框架(6个CPU过程+1个GPU过程)27.430.43712.0

2.3 做的更好 — 引入TensorRT优化减速

在反对推理服务接入\_kubeai-inference-framework\_对立框架的过程中,咱们持续尝试在模型自身做优化晋升。通过调研和验证,咱们将现有pth格局模型通过转成TensorRT格局,并开启FP16,在推理阶段获得了更好的QPS晋升,最高可到10倍晋升。

TensorRT是由英伟达公司推出的一款用于高性能深度学习模型推理的软件开发工具包,能够把通过优化后的深度学习模型构建成推理服务部署在理论的生产环境中,并提供基于硬件级别的推理引擎性能优化。业内最罕用的TensorRT优化流程,是把pytorch / tensorflow等模型先转成\_onnx\_格局,而后再将\_onnx\_格局转成TensorRT(_trt_)格局进行优化,如下图所示:

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

  • 网络构建期
  1. 模型解析与建设,加载onnx网络模型。
  2. 计算图优化,包含横向算子交融,或纵向算子交融等。
  3. 节点打消,去除无用的节点。
  4. 多精度反对,反对FP32/FP16/int8等精度。
  5. 基于特定硬件的相干优化。
  • 模型运行期
  1. 序列化,加载RensorRT模型文件。
  2. 提供运行时的环境,包含对象生命周期治理,内存显存治理等

为了更好地帮忙模型开发者应用TensorRT优化,KubeAI平台提供了***kubeai-trt-helper*** 工具,用户能够应用该工具把模型转成TensorRT格局,如果在模型转换的过程中呈现精度失落等问题,也能够应用该工具进行问题定位与解决。\_kubeai-trt-helper\_次要在两个阶段为用户提供帮忙:一个是问题定位,另一个阶段是模型转换。

问题定位

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

第1步:设定模型转换精度要求后,标记所有算子为输入,而后比照所有算子的输出精度。

第2步:找到最早的不合乎精度要求的算子,对该算子进行如下几种形式干涉。

  • 标记该算子为FP32。
  • 标记其父类算子为FP32。
  • 更改该算子的优化策略。

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

配置项释义
FP32\_LAYERS\_FOR_FP16开启FP16模式下,哪些算子须要额定开启FP32
TRT\_EXCLUDE\_TACTICTensorRT算子须要疏忽的tactic策略(tactic可参考TensorRT相干材料)
atol相对误差
rtol绝对误差
check-error-stat误差的计算方法包含:mean, median, max

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

2.4 落地实际成绩

在理论利用中,咱们帮忙算法域的模型开发同学,可能对一个推理基于自研推理服务对立框架进行实现的同时,也开启TensorRT优化,这样往往能够失去QPS两次优化的叠加成果。

2.4.1 分类模型,CPU与GPU拆散,TensorRT优化,并开启FP16,失去10倍QPS晋升

线上某个基于Resnet的分类模型,对精度损失能够承受误差在0.001(误差定义:median,atol,rtol)范畴内。因而咱们对该推理服务进行了3项性能优化:

    1. 应用\_kubeai-inference-framework\_对立框架,对CPU过程和GPU过程进行拆散革新。
    2. 对模型转ONNX后,转TensorRT。
    3. 开启FP16模式,并应用自研工具定位到两头呈现精度损失的算子,把这些算子标记为FP32。

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

2.4.2 检测模型,CPU与GPU拆散,TensorRT模型优化,QPS晋升4-5倍左右。

线上某个基于Yolo的查看模型,因为对精度要求比拟高,所以不能开启FP16,咱们间接在FP32的模式下进行了TensorRT优化,并应用\_kubeai-inference-framework\_对立框架对GPU过程与CPU过程拆散,最终失去QPS 4-5倍的晋升。

2.4.3 模型推理过程多实例化,充分利用GPU算力资源

在理论的场景中,往往GPU的算力是短缺的,而GPU显存是不够的。通过TensorRT优化后,模型运行时须要的显存大小个别会升高到原来的1/3到1/2。所以为了充分利用GPU算力,\_kubeai-inference-framework\_对立框架进一步优化,反对能够把GPU过程在一个容器内复制多份,这种架构即保障了CPU能够提供短缺的申请给GPU,也保障了GPU算力充分利用。

线上某个模型,通过TensorRT优化后,显存由原来的2.4G升高到只须要1.2G。在放弃推理服务配置5G显存不变的状况下,咱们将GPU过程为复制4份,充分利用了5G显存,使得服务吞吐达到了原来的4倍。

3.AI训练引擎优化实际

3.1 PyTorch框架详情

PyTorch是近年来较为火爆的深度学习框架,简直占据了CV(Computer Vision,计算机视觉)、NLP(Natural Language Processing,自然语言解决)畛域各业务方向,算法同学根本都在应用PyTorch框架来进行模型训练。下图是基于PyTorch框架进行模型训练时的代码根本流程:

第1步:从pytorch dataloader中将本step训练过程中须要的数据拉进去。

第2步:将获取到的数据,例如:样本图片、样本标签的tensor等数据,复制到GPU显存里。

第3步:开始正式的模型训练:前向计算、计算损失、计算梯度、 更新参数。

整个训练过程的耗时,也次要散布在下面3个步骤。通常第2步不会是瓶颈,因为大部分训练样本图片都是被resize变小之后才从内存拷贝到到GPU显存上的。但因为模型的差异性、训练数据的差异性,常常是第1、2步会在训练过程中呈现性能瓶颈,导致训练耗时长,GPU利用率低下,影响模型迭代效率。

3.2 Dataloader瓶颈剖析及优化

3.2.1 PyTorch Dataset/Dataloader剖析

PyTorch训练读取数据局部次要是通过Dataset、Dataloader的形式实现的,其中Dataset为用户自定义读取数据的类(继承自 torch.utils.data.Dataset),而Dataloader是PyTorch实现的在训练过程中对Dataset的调度器。

torch.utils.data.Datasettrain_loader = torch.utils.data.DataLoader( MyDataset,batch_size=16,num_workers=4,shuffle=True,drop_last=True,pin_memory=False)val_loader = torch.utils.data.DataLoader(MyDataset,batch_size=batch_size,num_workers=4,shuffle=False)

参数解释如下:

  • dataset(Dataset):传入的自定义Dataset(数据读取的具体步骤)。
  • batch_size(int, optional):每个batch有多少个样本,每个iter能够从dataloader中取出多少数据。
  • shuffle(bool, optional):在每个epoch开始的时候,对数据进行从新排序,能够使每个epoch读取数据的组合和程序不同。
  • num_workers (int, optional):这个参数决定dataloader启动几个后盾过程来做数据拉取。0意味着所有的数据都会被load进主过程,默认为0。
  • collate_fn (callable, optional):将一个list的sample组成一个mini-batch的函数,个别CV场景是concat函数。
  • pin_memory (bool, optional):如果设置为True,那么data loader将会在返回batch之前,将tensors拷贝到CUDA中的固定内存(CUDA pinned memory)中, 这个参数某些场景下有妙用。
  • drop\_last (bool, optional):该参数是对最初的未实现的batch来说的,比方batch\_size设置为64,而一个epoch只有100个样本,如果设置为True,那么训练的时候前面的36个就被扔掉了,否则会持续失常执行,只是最初的batch_size会小一点。默认设置为False。

上述参数中,比拟重要的是num_workers,Dataloader在结构的时候,会启动num_workers个worker过程,而后主过程会向worker过程散发读取工作,worker过程读到数据之后,再把数据放到队列中供主过程取用。多过程模式应用的是torch.multiprocessing接口,能够实现worker过程与主过程之间共享内存,而且共享内存中能够寄存tensor,这样过程中如果返回tensor,能够通过共享内存的形式间接将后果返回给主过程,缩小多过程间的通信开销。

num_workers 为0 的时候: get_data()流程与train_model()过程是串行,效率十分低下,如下图所示:

num_workers 大于0开启多过程读取数据, 并且读取一个batch数据的工夫小于一个step训练的工夫时效率最高,GPU算力被充分利用,如下图所示:

num_workers 大于0开启多进水平数据, 然而读取一个batch数据的工夫大于一个step训练的工夫时,会呈现GPU训练过程期待数据拉取,就会呈现GPU算力闲暇,训练耗时减少,如下图所示:

由此可见Dateset中的__getitem__函数十分重要,详细分析它的源码实现后咱们发现,该函数的耗时次要蕴含2段时间:

  • load\_image\_time:从磁盘或者近程盘上读取数据的耗时。
  • transform\_image\_time:将图片或文本数据进行预处理的耗时。

3.2.2 解问题 — 设置正当的参数很重要

通过上一大节的剖析,训练时相干参数的抉择至关重要。总结如下:

  • batch_size:依据数据量,以及冀望训练时长,用户正当自定义设置
  • 训练环境(KubeAI Notebook/工作/流水线节点)的CPU配置:倡议CPU配置为 GPU卡数*(单GPU卡配置的CPU核数)。
  • num_workers:参数最小设置为 训练环境的CPU配置-1,比方:工作配置为12C时,倡议该参数设置为11 。另外,num_workers数值能够适当调大,因为dataset iter中有局部工夫是在网络或者磁盘IO, 这部分不耗费CPU;然而也不能设置太大,因为数据预处理局部是CPU密集型工作,并行过程过多,会造成CPU争抢从而升高预处理效率。
优化案例一

线上一个基于MMDetection框架(其底层也是调用PyTorch框架)的CV模型训练任务,在做参数调整之前,单个step耗时不稳固,均匀在1.12s左右,其中拉取数据时长在0.3s左右:

mmengine - INFO - Epoch(train) [2][3100/6005] time: 1.0193 data_time: 0.0055mmengine - INFO - Epoch(train) [2][3150/6005] time: 1.0928 data_time: 0.3230mmengine - INFO - Epoch(train) [2][3200/6005] time: 0.9927 data_time: 0.2304mmengine - INFO - Epoch(train) [2][3250/6005] time: 1.3224 data_time: 0.5135mmengine - INFO - Epoch(train) [2][3300/6005] time: 1.1044 data_time: 0.3427mmengine - INFO - Epoch(train) [2][3350/6005] time: 1.0574 data_time: 0.2842

调整参数之后,单个step耗时稳固,均匀在0.78 s左右,其中拉取数据耗时0.004s,根本能够疏忽。

mmengine - INFO - Epoch(train) [1][150/5592] time: 0.7743 data_time: 0.0043mmengine - INFO - Epoch(train) [1][200/5592] time: 0.7736 data_time: 0.0044mmengine - INFO - Epoch(train) [1][250/5592] time: 0.7880 data_time: 0.0044mmengine - INFO - Epoch(train) [1][300/5592] time: 0.7761 data_time: 0.0045

该模型训练任务,通过上述优化调整,数据拉取工夫缩短为0,单个step的耗时从原来的1.12s降到0.78s,整体训练工夫缩小30%(从2天缩短到33小时),效果显著。

优化案例二

线上某个多模态模型(输出蕴含图片和文字)训练任务,应用2卡V100训练,参数调整如下:

CPU = 12 ---> 调整为 24num-workers = 4 ---> 调整为 11

调整后训练300 step总耗费时405s,整体训练工夫缩小45%左右(从10天缩短到5天左右)。

优化案例三

线上某YoloX模型训练任务,应用单卡A100训练,参数调整如下:

num-workers = 4 ---> 调整为 16

调整后整体训练时长缩小80%左右(从10天19小时,缩短至1天16小时)。

3.2.3 数据拉取IO瓶颈剖析

以后,KubeAI平台为训练场景提供3种存储介质:

  • 本地盘:空间小,读写性能最好,单盘500G~3T空间可用。
  • NAS网络存储:空间大,读写性能较差,老本适中。
  • CPFS并行文件系统存储:空间大,读写性能好,老本高。

对于小数据集,能够先将数据一次性拉取到本地盘,而后每个epoch从本地盘来读数据,这样防止了每一个epoch反复的从近程NAS来拉取数据,相当于整个训练只须要从近程NAS拉取一次数据。对于大数据集,有2种解决方案:

  • 将大数据集提前进行resize,存储比拟小的图片来进行训练,这样防止了每个epoch都须要resize,而且resize之后,图片变小,读取更快。
  • 将数据集放入并行文件系统CPFS存储上,进步训练吞吐。试验表明CPFS 在图片场景下是NAS盘读性能的3~6倍。

3.3 TrainingModel优化

数据局部优化后,训练过程中的次要工夫开销就在GPU训练局部了。目前业内有一些比拟成熟的办法能够参考,咱们总结如下。

3.3.1 混合精度训练(AMP)

PyTorch混合精度训练在PyTorch官网有具体介绍,以及开启混合精度训练的办法,能够浏览这里获取实现办法。以后许多CV训练框架曾经反对AMP训练,比方:

  • MMCV框架中AMP参数就是开启混合精度训练的选项。
  • Pytorch Vision中也有相干参数来开启AMP训练。

须要阐明的是,混合精度训练过程中并不是将所有模型参数都转为FP16来计算,只有局部做转换。混合精度之所以能减速训练过程,是因为大部分英伟达GPU机型在FP16这种数据格式的浮点算力比FP32要快一倍;此外,混合精度训练显存占用会更小。

for epoch in epochs:for input, target in data:optimizer.zero_grad()with autocast(device_type='cuda', dtype=torch.float16):output = model(input)loss = loss_fn(output, target)scaler.scale(loss).backward()# Unscales the gradients of optimizer's assigned params in-placescaler.unscale_(optimizer)# Since the gradients of optimizer's assigned params are unscaled, clips as usual:torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)# optimizer's gradients are already unscaled, so scaler.step does not unscale them,# although it still skips optimizer.step() if the gradients contain infs or NaNs.scaler.step(optimizer)# Updates the scale for next iteration.scaler.update()

3.3.2 单机多卡数据并行训练

Pytorch原生反对多卡数据并行训练,具体开启多卡训练的形式参考官网文档。多卡训练过程中每一张卡的backword计算会多加一次多卡之间汇合通信all-reduce操作,用来计算多张卡上的梯度的平均值。

3.4 自研训练引擎框架kubeai-training-framework

通过后面的剖析咱们能够看到,尽管PyTorch框架自身曾经做的很好了,训练形式、参数反对丰盛,但在理论的模型钻研和生产过程中,因为模型的差异性、训练数据的差异性,以及模型开发者的教训差异性,PyTorch框架自身的劣势不肯定可能施展进去。

基于前述剖析和实际,KubeAI平台开发了训练引擎框架kubeai-training-framework,帮忙模型开发者更好地匹配训练脚本参数,疾速接入应用适合的训练形式。\_kubeai-training-framework\_中蕴含PyTorch Dataloader优化、GPU TrainModel(AMP)提速以及各种性能函数等。以Dataloader为例,用户可通过以下形式应用:

from kubeai_training_framework.dataloader import Dataloaderdef train(train_loader, model, criterion, optimizer, epoch):train_dataset = .......train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True,num_workers=args.workers, pin_memory=True)model.train()my_train_loader = Dataloader(train_loader)input, target = my_train_loader.next()while input is not None:## model train 代码 input, target..........input, target = my_train_loader.next()

4.AI Pipeline引擎助力AI业务疾速迭代

通常模型的开发能够演绎为如下图所示的过程:

能够看到,在需要场景确定、第一个模型版本上线之后,模型是须要重复迭代的,以冀望获得更好的业务成果。KubeAI平台在迭代建设的过程中,逐渐上线了Notebook、模型治理、训练任务治理、推理服务治理等一个个绝对独立的功能模块。随着业务需要的一直变动,模型迭代效率间接影响了业务的上线效率,KubeAI平台建设了AI Pipeline能力,重点解决AI场景的周期性迭代类需要,进步生产效率。

AI Pipeline是在ArgoWorkflow根底上做了二次开发,以满足模型迭代、推理工作治理、数据处理等对定时需要、工作启动触发形式、通用模板工作、指定节点启动等需要。AI Pipeline上线之前,一个迭代工作可能会被配置为多个扩散的工作,保护工作量大,调试周期长。如下图是做一个相似工作须要独自配置的工作状况:

AI Pipeline能够将整个工作流设计成如下图所示:

Pipeline编排的形式,缩小了模型开发者节约在反复工作上的工夫,能够将更多的工夫投入到模型钻研上。同时,通过正当编排工作,能够对无限的资源进行充沛地利用。

5,瞻望

KubeAI平台从得物AI业务场景的理论需要登程,以三大外围引擎为建设指标,着力解决AI模型研发过程中的训练、推理性能问题,以及模型版本迭代过程中的效率问题。

在推理服务性能上,咱们会以kubeai-inference-framework为终点,持续在模型量化、算子优化、图优化等方面进行深刻摸索。在模型训练方面,咱们会持续在图像数据预处理、Tensorflow GPU训练框架反对、NLP模型训练反对上发力,以kubeai-training-framework训练引擎框架为接口,为模型开发者提供更高效、性能更高的训练框架。此外,AI Pipeline引擎上,咱们会反对更丰盛的预置模型,以满足通用数据处理工作、推理工作等需要。

文:伟东

本文属得物技术原创,来源于:得物技术官网

未经得物技术许可严禁转载,否则依法追究法律责任! 著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。