Fork me on GitHub

vivo 技术|在推荐业务中如何用 MPS 提高 GPU 利用率?

导读: 推荐场景下,使用传统的 CPU 推理方案可能出现计算量太大的问题,直接使用 GPU 又会遇到 GPU 利用率不高的问题,为此我们引入了 MPS 技术来解决上述问题。

今天会和大家分享一下MPS相关的技术和应用,主要包括以下内容:

  • 为什么我们会选用 MPS?
  • 什么是 MPS 技术?
  • 我们是怎么使用 MPS 技术的?
  • MPS 技术表现
  • 使用 MPS 技术需要注意的点

分享嘉宾|陈名华 vivo AI研究院 AI架构工程师
编辑整理|马朝威 广东外语外贸大学
出品平台|DataFunSummit


01 为什么我们会选用 MPS?

1. 我们遇到的问题

首先来讲一下我们遇到的问题。

现在大部分的推荐业务都是使用 CPU 进行推理,但CPU 推理效率不够。我们使用的 CPU 的量比较多,如果模型稍微复杂一些,单台 CPU 的 QPS 会很低。

然而,直接使用 GPU 又会遇到利用率上不去的问题。一些任务的计算量可能对于CPU 是很大的,但对于 GPU 却是非常小的。我们传统的方式是用某个推理引擎,用单进程的方式跑推理过程,因为本身 GPU 和 CPU 的运算方式就不同,GPU 是准备一个批量,然后进行一次运算,也就是吞吐量会很大,但是单次的推理过程运算量比较小,GPU 的利用率就非常低。这样会出现使用 CPU 算力不够,用 GPU 又利用率不高的尴尬境地。

2. 我们做过的尝试

面对这样的问题,我们做过一些尝试,主要是用 Triton、TensorRT Backend 和拆图的方式。首先,我们处理一些很小的请求时,没办法充分利用 GPU,就很容易想到用动态 Batch,把请求 Batch 到一起,这是 Triton 自带的一个特性。我们用的框架是 TensorFlow 框架,在推理上会做优化,所以理论上性能是比较好的。但是 TensorFlow 算子是非常丰富的,而 TRT 能支持的算子不是那么丰富,这就意味着无法直接从 TensorFlow 转到 TRT。

另外,TRT 在推荐场景里面,是有一些限制的。推荐场景中有些特征是可变序列的,无法 Batch,Batch 到一起就不可拆分了。为此,一个策略就是拆图,我们把 TensorFlow 的计算图拆开来,把能转换的部分,比如 DNN 部分,就用 TRT 来做,不能转换的部分就用 CPU 来跑。

实验表明,拆图之后过程就变长了,而且推理性能也没有 TRT 理论上的性能那么理想。更重要的是,拆图对业务是非常不友好的。拆图是靠人工去看那些计算图,找到合适的点拆出来,甚至有时候要调整图结构,要把一些算子反复调去做转换,这个过程是非常繁琐的,最后导致没办法支撑很多项目。这还会变成一种限制,如果算法工程师要优化模型,需要调整图结构,可能做了一些简单的调整之后,整个图都得重新拆或者重新来调整。这些原因导致没有办法大规模地在推荐上面使用 Triton + TRT 的方案。

3. 契机和新的尝试

由于团队变动,我开始重新 Review 之前的测试,当我 Review 到 MPS 技术的时候,发现值得深入调研。我重新对 MPS 做了测试,重新再评估这套技术方案。评估结果显示 MPS 是非常适合我们的推荐场景的。

02 什么是 MPS 技术?

接下来讲一下什么是 MPS 技术。

图片

MPS 技术是一种并发使用 GPU 的技术,全称是 Multi-Process Service,通过多进程的方式,让每个进程的业务都可以并发地使用一张 GPU 卡,从而提高 GPU 利用率和整个服务的吞吐量。 目前主要有三条提高 GPU 利用率的策略 ,一是通过动态 Batch 就把多个请求结合成一个,依次推理,这样还是在单个进程里面用一个 Context;二是起多个进程,同时提供服务,里面每个都是一个模型实例,这些多进程可以通过 MPS 技术,同时把任务提交给 GPU,GPU 会对这些资源进行隔离和限制,并发地运行里面的算子,从而达到动态 Batch 类似的效果;三是最新的 ** GPU 支持** ,类似硬件级别 GPU 虚拟化。

MPS底层有多个 Context,一个进程只能用到一个提交任务的 Context,多个进程就多个 Context 同时并发地把任务提交进来,那么多个模型的推理过程就都可以并发运行了。同时又不需要做动态 Batch,也就规避掉了模型转换,以及动态 Batch 的一些限制,或是可变序列不支持的情况。

测试表明,MPS 技术能够非常有效地提高 GPU 利用率。

图片

这个方案还有一个 优点 ,MPS 技术本质上是在 Cuda Driver 层的一个技术, 应用层程序是完全感知不到的 。上图是 MPS 架构简图。MPS 会有一个控制进程,为每一张 GPU 卡新建一个 Server 进程,MPS 控制这些 Server 进程,而这些 Server 进程负责 Client 端的交互,提交任务给 GPU。

MPS 是一种通用技术,兼容性强,可以跟 Triton 结合,也可以和 Tensorflow、PyTorch 结合的。

图片

上图是我们内部实现的一些进程模型。

03 我们是怎么使用 MPS 技术的?

下面介绍一下我们是如何使用 MPS 技术的。

图片

1. 推理引擎逻辑

MPS 技术本身是可以与 Triton 结合的。但是我们的推荐场景比较特殊,有一些 Batch 是可变序列的,比如推荐项目中的某些特征是动态的,没办法动态 Batch,因此 Triton 的帮助不是很大。另外,Triton+TensorRT+拆图的成本非常高,也不符合我们的场景。我们还尝试过跟 Tensoflow 原生的 Tensoflow Backend 做结合,但性能收益也很有限。基于以上原因,我们自研了推理引擎。

首先,我们使用的是 多进程模型 ,这意味着多个进程同时提供服务时,这些进程是需要有被守护的。因此有一个守护进程,守护我们的服务进程。上图中的每个 Worker 是一个服务进程,比如上图的两个 Worker 跑在同一个 GPU 卡上,也就是两个进程共享一张 GPU 卡。MPS Control,就是前面讲到过的控制进程,守护进程也会守护 MPS Control。业务进程通过的 Http 接口暴露监控数据给 Prometheus 监控系统。每个 Server 通过 GRPC 对外提供服务,GRPC 服务启动时会自己注册到VNS。VNS 是基于 NACOS 研发的一个服务注册和发现系统。通过这种方式来做服务发现和动态负载。



还有一个服务进程是 模型管理的进程 。我们的模型是存储在 HDFS 里面的。我们内部现在用的是大模型的方案,会持续的去训练更新模型。我们把模型分成两部分,比如一个很大的推荐模型训练之后,一部分放在参数服务器里面,另外一部分是 DNN 部分,放到 HDFS 里面。我们会动态监听 HDFS 的路径,动态地进行模型的加载和替换。

以上就是整体的推理引擎逻辑。

2. Rust 语言

我们自研推理引擎是用 Rust 语言实现的。使用 Rust 语言的原因是,Rust 语言的学习成本较低,开发效率很高,基本上只要能编译通过,服务就会非常健壮。

3. 算子管理

我们现在的方案是直接使用原生的 Tensorflow GPU Backend,从而规避掉了拆图转换操作。 我们用多进程加 MPS 技术来提升 GPU 利用率,进而提升推理性能。Tensorflow 绝大多数的算子都是支持的,有极少数算子没有 GPU 实现。Tensorflow 有一个 FallBack 机制,如果计算图中所有的算子都是 GPU 支持的,就直接都在 GPU 上推理,性能很高;如果里面有一个算子不是 GPU 支持,那么就会把前面运算的输出放到内存里面,然后调用 CPU 的算子,把结果再拷进 GPU,再执行 GPU 的算子。这使得 Tensorflow 有着很好的兼容性。如果有算子不支持 GPU,那么就不能很好地利用 GPU,所以我们自己实现了这部分算子。因此,我们的推理引擎中还包括算子管理。

4. 运行环境

图片

VIVO 的环境是混合的,包括物理机运行环境,K8s 运行环境。

MPS 技术跟物理机环境天生就是结合到一起的。启动一个 Control 进程,每一张物理机的 GPU 卡会自动启动一个 Server 进程,只要程序跑起来就可以自然利用多张 GPU 卡。

现在 VIVO 的策略是慢慢地把物理机器往 K8s 转。因为容器技术的运维成本比较低,而且对于资源的分配、提高利用率也是有极大帮助的。K8s 环境使用 MPS 技术有两套策略。一套策略是,在物理机上启动一个 MPS Control 进程,每个 Pod 分一张 GPU 卡,启动每个 Pod 的时候打开 IPC 进程通讯,通过这种方式共用一个物理机的 Control 来使用 MPS 技术。这套策略的弊端在于,它破坏了 K8s 的安全性,因为要打开进程间通讯,容器的 NameSpace 用的是物理机的 NameSpace,没办法做很好的隔离。

另一套策略如上图中所写,一个容器,也就是一个 Pod 分一张 GPU 卡,起一个Control 进程,这个 Control 进程负责管理这张卡,为这张卡起一个 MPS server,里面跑多个推理进程。这样 K8s 就可以正常的隔离,也不需要调整 K8s 的权限系统,Pod 的管理也比较方便。

04 MPS 技术表现

这部分分享一下我们使用 MPS 技术获得的收益。

1. 解决推荐业务中 GPU 通用问题

最大的收益是解决了通用性问题。推荐业务一般都迭代非常快,模型更新可能都是分钟级别的。使用 MPS 技术,通过多进程的方式,规避了动态 Batch,通过多进程的方式提高 GPU 利用率。同时,由于没有转换模型,用原生 TensorFlow 自带 GPU Backend,通用性很高。

2. 提高 GPU 利用率和业务吞吐量

图片

上图是我们做的一个项目的测试结果。测试的数据 Batch Size 都是 2000,即一个请求里面包含 2000 条数据,在特征处理的时候把 2000 条合在一起。并发数是client端发起请求的并发数。GPU 卡数是测试时使用了多少张 GPU 卡。我们测试了三种方案,第一个是通用 GPU 方案,即我们自研的方案,第二个是 TensorRT 方案,也就是前面说过的拆图的方案,第三个是 CPU 推理方案。

通用 GPU 方案的 QPS 可以达到 218,它的平均延迟, P95 延迟,还有 P99 延迟,都是比较优秀的;TensorRT 方案也有不错的 QPS,但相对于通用 GPU 方案还是有些差距,延迟也会略高一些;CPU 推理方案的 QPS 非常低,延迟非常严重。

从测试结果来看,通用 GPU 方案是非常有效的,同时也兼顾了通用性。理论上讲,TensorRT 方案应该会更强,但可能由于推荐业务中计算图的结构比较简单,优化的点比较有限,因此我们的通用 GPU 方案略胜一筹。TensorRT 可能在计算密集型,比如图像卷积网络之类的,就非常有优势。

3. 降低成本

图片

上图是在成本上的一些表现,对比的还是刚才的三种方案。A1 机器是我们内部的一种CPU 机型,是 64 个超线程,32 个物理核,内存 250G。我们使用 CPU 推理方案的成本作为 Baseline。通用 GPU 方案是多个进程,同时暴露GRPC 的服务。使用通用的 GPU 方案,必须有一台配有一张 GPU 卡的机器,还要配一些其他的一些做特征处理的服务,我们用 16 台物理机配和一台 T4 机器。TensorRT 方案或 CPU 推理方案是直接加载模型,通过 JAVA 写的推理服务。在同样 QPS 情况下,相较于 CPU 推理方案,通用 GPU 方案成本下降 75%,相较于 TensorRT 方案成本下降 14%。

综合以上测试结果,通用 GPU 方案做推荐业务的推理,无论是在成本上,还是在性能上,都是非常有优势的,同时在延迟、通用性上也有着很大的优势。

05 使用 MPS 技术需要注意的点

1. 需要注意的点

使用 MPS 技术有以下一些需要注意的点:

  • 显卡架构要等于或新于 Volta :MPS 技术有不同的版本,在 Volta 这种架构之前,它的性能不是那么好,如果要用 MPS 技术,架构版本应该要高于 Volta。现在基本上新的 GPU 架构版本都是高于 Volta,所以大家也不用太担心。我们用的主要是 T4 机器。
  • 需要较新的 CUDA Driver 版本 :只要是稳定的,版本越新越好。
  • 结合 K8s 策略选择 :要基于内部的需求去选择合适的策略。
  • Tensorflow 不是所有的算子都支持 GPU,可能需要开发少量的算子 :如果用原生的 Tensorflow backend,GPU 利用率很高,CPU 利用率也很高,但是 QPS 上不去,就要怀疑是否有算子回落到了 CPU 上,这样就需要将该算子改成支持 GPU 的。

2. 使用场景

MPS 技术适合的场景包括:

  • 模型体积不太大 :如果模型特别大,可能一张 GPU 卡都加载不下,我们的场景中,推荐模型如果将 Embedding 拆出去体积就很小了;
  • GPU 使用率低 :比如深度网络不是那么复杂, GPU 利用率上不去、吞吐量上不去的情况;
  • 无法轻松转换成 TensorRT :比如通用性问题,涉及人工改图,或有些算子不易实现的情况。

|分享嘉宾|
图片

陈名华

vivo AI研究院 AI架构工程师

吉林大学毕业,曾任职于阿里巴巴,加入过创业大军,目前任职于vivo AI研究院从事架构工作。


本文地址:https://www.6aiq.com/article/1667486265744
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出