前述
在我们的新闻推荐业务中,除推荐模型外还有很多cv、nlp、llm模型,例如对推荐内容进行理解、分类、实体识别、生成标签、生成embedding向量或生成自然语言回答等场景。这些模型经常需要进行独立部署提供服务。以往每个模型是自己搭建一个http服务,这存在很多问题:
- 没有统一服务框架和指标监控框架,代码比较随意。
- 性能一般较差,包括接口层以及推理层。
- 服务不稳定,经常会发生重启现象。
- 高显存低GPU使用率问题。
- 另外由于GPU资源限制,qps规模不是很大的模型需要通过共享的方式共享所有的GPU,因此出现了很多显存无限增长导致的显存溢出、服务发布容易失败的问题。
为此我们开发了一套通用的模型推理服务框架。(帮忙点一个star就是最好的鼓励^_^)GRPS(Generic Realtime Prediction Service)https://github.com/NetEase-Media/grps
支持tensorflow/torch/tensorrt/vllm以及更多nn框架,支持dynamic batching、streaming模式,可限制、可拓展、高性能。核心目的是帮助用户快速搭建一个稳定的较好性能的在线模型推理服务,将模型部署到线上生产环境,并通过HTTP/RPC接口方式提供服务。
特性
易用
内置tensorflow、torch以及tensorrt以及扩展更多模型推理后端,可以实现一键快速部署服务,自定义拓展简单,支持Python与C++编程语言。
通过简单的命令(tf_serve/torch_serve/trt_serve)即可快速启动一个tf、torch、trt模型服务,快捷启动方式启动的服务由纯C++实现。例如通过如下命令即可启动一个torch resnet-50模型。
# 使用默认参数启动服务
grpst torch_serve ./resnet50_pretrained.pt
# 使用grpst ps查看服务状态
grpst ps
# 输出如下:
PORT(HTTP,RPC) NAME PID DEPLOY_PATH
7080,7081 my_grps 138949 /root/.grps/my_grps
并且通过命令可以方便的设置接口模式、服务限制(显存限制、并发限制)、打开dynamic batching模式、设置端口号、选择GPU卡、以及日志路径等:
# 指定参数启动
grpst torch_serve /tmp/script_model.pt \ # 模型文件路径
--name my_grps \ # 设置服务名称
--interface_framework http+grpc \ # 设置接口模式,同时打开http和grpc接口模式
--port 7080,7081 \ # 设置http和grpc接口的端口号
--device gpu:4 \ # 设置模型运行的设备类型
--batching_type dynamic \ # 打开dynamic batching模式
--max_batch_size 16 \ # 设置dynamic batching max batch size为16
--batch_timeout_us 2000 \ # 设置batching组装超时时间为2ms
--max_connections 100 \ # 设置最大连接数
--max_concurrency 10 \ # 设置最大推理并发数
--gpu_mem_limit_mib 4096 \ # 设置模型运行的显存限制
--gpu_mem_gc_enable \ # 设置开启显存定时垃圾回收
--gpu_mem_gc_interval 60 \ # 设置显存定时垃圾回收的时间间隔,单位为秒
自定义拓展
用户可以自定义自己的前后处理、模型推理,提供py和c++两种编程语言自定义工程。通过如下命令快速创建自定义工程:
# 创建自定义工程
grpst create my_grps
# 选择自定义工程模板,如下提示,也可以直接使用--project_type参数指定。
Select project type.
[1] "py": python project with pytorch and tf support.
[2] "cpp": c++ project without libtorch and libtf support.
[3] "cpp_torch": c++ project with libtorch support.
[4] "cpp_tf": c++ project with libtf support.
[5] "cpp_trt": c++ project with libtensorrt support.
Please input number(1-5), default is "1":
py工程核心自定义:
# 前处理
def preprocess(self, inp: GrpsMessage, context: GrpsContext):
pass
# 后处理
def postprocess(self, inp, context: GrpsContext) -> GrpsMessage:
pass
# 推理
def infer(self, inp, context: GrpsContext):
pass
c++工程核心自定义:
// 前处理
void PreProcess(const ::grps::protos::v1::GrpsMessage& input,
std::vector<std::pair<std::string, TensorWrapper>>& output,
GrpsContext& ctx) override;
// 后处理
void PostProcess(const std::vector<std::pair<std::string, TensorWrapper>>& input,
::grps::protos::v1::GrpsMessage& output,
GrpsContext& ctx) override;
// 推理
void Infer(const std::vector<std::pair<std::string, TensorWrapper>>& inputs,
std::vector<std::pair<std::string, TensorWrapper>>& outputs,
GrpsContext& ctx) override;
通过如下命令进行快速打包与部署:
# 打包会首先执行单测,单测通过后才会打包
grpst archive .
# 启动服务
grpst start server.mar
# 使用grpst ps查看服务状态
grpst ps
# 输出如下:
PORT(HTTP,RPC) NAME PID DEPLOY_PATH
7080,7081 my_grps 138949 /root/.grps/my_grps
Batching模式支持
支持dynamic batching,充分利用GPU资源,提高推理性能。dynamic batching模式下,对于请求不会立即处理,而是通过动态组装batch tensor的方式合并请求进行一次模型推理,这样可以充分使用GPU计算资源,对于吞吐量的提升比较明显。
以resnet-50模型为例,在K80 GPU上的测试数据如下:
在较小的并发情况下由于batch组装的影响会略微影响性能,在并发较大时组装batch耗时可以忽略,dynamic batching模式对性能和吞吐量提升很大,8并发之后大致可以获得30%+的提升。
Tensorrt多流支持
内置tensorrt推理后端支持多流配置,通过创建多个tensorrt context以及stream进行并行多stream推理,可以更好的利用CUDA多流推理的机制,提高GPU使用率以及推理性能。
# trt_serve 快捷部署参数
--streams 4 # 打开多流配置可以提高GPU使用率以及推理性能,相应也会增加GPU显存占用。
# 自定义工程配置(inference.yml)
models:
- name: your_model
...
inferer_type: tensorrt
...
inferer_args:
streams: 4
...
以resnet-50模型为例,在K80 GPU上的测试数据如下:
LLM支持
目前通过自定义后端插件方式支持vllm,见vllm样例,通过LLMEngine Api实现vllm推理后端,同vllm-api-server相比较服务性能有所提升,例如通过THUDM/chatglm3-6b做测试。
可限制
通过配置化方式可以实现tf和torch显存限制功能,适用于共享GPU场景,很好的解决了由于共享GPU导致的显存无限增长,发布困难等问题。也可以限制服务的请求并发度,对于限制服务的CPU使用率、GPU使用率、以及主机内存都有较好的帮助。例如:
# tf_serve/torch_serve/trt_serve 快捷部署参数
--max_connections 100
--max_concurrency 10
--gpu_mem_limit_mib 4096
# 自定义工程配置(server.yml)
max_connections: 1000
max_concurrency: 32
gpu:
devices: [0]
mem_manager_type: torch
mem_limit_mib: 4096
可监控
提供用户日志系统,内置指标监控系统,内置简洁的web图形展示页面。服务启动后会自动启动一个内置monitor服务,通过访问服务的http根路径即可访问,monitor页面可以看到服务指标的变化,例如qps、latency、GPU使用率、GPU显存、CPU使用率与内存占用率等。用户也可以通过monitor api拓展自己的指标。
变化趋势:
cdf展示:
灵活访问方式
自动适配http、gRPC、bRPC访问方式,提供py、c++、java等客户端样例。例如通过如下配置可以修改服务接口模式:
# tf_serve/torch_serve/trt_serve 快捷部署参数
--interface_framework http+grpc
# 自定义工程配置(server.yml)
interface:
framework: http+grpc
host: 0.0.0.0
port: 7080,7081
Streaming模式支持
支持模型持续推理并通过streaming的方式返回结果给客户端,适用于自然语言生成、视频处理等场景。例如如下http streaming返回样例:
b'{\n "status": {\n "code": 200,\n "msg": "OK",\n "status": "SUCCESS"\n },\n "str_data": "this process"\n}\n'
b'{\n "status": {\n "code": 200,\n "msg": "OK",\n "status": "SUCCESS"\n },\n "str_data": "cause"\n}\n'
b'{\n "status": {\n "code": 200,\n "msg": "OK",\n "status": "SUCCESS"\n },\n "str_data": "dog"\n}\n'
多模型支持
支持部署多个模型,多个模型可以组合成一个服务或者单独提供服务。通过如下参数进行选择模型:
# http接口通过query参数model选择模型:
curl -X POST -H "Content-Type:application/json" -d '{"str_data": "hello grps"}' 'http://ip:port/grps/v1/infer/predict?model=your_model-1.0.0'
# rpc接口通过请求消息的model参数选择模型:
{
"model": "your_model-1.0.0",
"str_data": "hello grps"
}
通过如下方式也可以组合模型提供服务:
# inference.yml
dag:
type: sequential # only support `sequential` now.
name: your_dag # dag name.
nodes: # sequential mode will run node in the order of nodes.
- name: node-1
type: model
model: your_model-1.0.0
- name: node-2
type: model
model: your_model2-1.0.0
多卡支持
支持配置方式选择gpu部署模型,支持监控多gpu使用情况。例如如下配置:
# inference.yml配置模型部署在哪个设备上。
models:
- name: your_model
version: 1.0.0
device: cuda:0 # device of model inferer. like `cpu`, `cuda`(==`cuda:0`), `gpu`(==`cuda:0`), `cuda:0`, `gpu:0`, `original`(original device specified when exported model).
# server.yml可以配置监控多个GPU使用情况,包含GPU使用率或GPU显存。
gpu:
devices: [0] # Devices will be monitored.
业务价值
服务上线更快
算法开发同事只需集中精力关注具体模型部分的训练和推理开发,不需要再关注服务框架相关的代码开发,例如接口层、日志层、监控层等,统一规范服务部署流程,使得模型服务部署周期更短。
工程代码更加规范
以往不同模型服务的代码各自开发,代码质量参差不齐。GRPS通过统一的模板工程以及规范化模型部分代码开发使得自定义开发代码更加规范,代码阅读和分析更加高效。
对于自定义开发,用户只需要关心模型前后处理(抽象为converter)以及模型推理(抽象为inferer)部分的代码开发。他们之间的关系如下:
Python自定义开发模板如下:
|-- client # 客户端样例
|-- conf # 配置文件
| |-- inference.yml # 推理配置
| |-- server.yml # 服务配置
|-- data # 数据文件
|-- docker # docker镜像构建
|-- src # 自定义源码
| |-- customized_converter.py # 自定义前后处理转换器
| |-- customized_inferer.py # 自定义推理器
|-- grps_framework-*-py3-none-any.whl # grps框架依赖包,仅用于代码提示
|-- requirements.txt # 依赖包
|-- test.py # 本地单元测试
C++自定义开发模板:
|-- client # 客户端样例
|-- conf # 配置文件
| |-- inference.yml # 推理配置
| |-- server.yml # 服务配置
|-- data # 数据文件
|-- docker # docker镜像构建
|-- second_party # 第二方依赖
| |-- grps-server-framework # grps框架依赖
|-- src # 自定义源码
| |-- customized_converter.cc/.h # 自定义前后处理转换器
| |-- customized_inferer.cc/.h # 自定义推理器
| |-- grps_server_customized.cc/.h # 自定义库初始化
| |-- main.cc # 本地单元测试
|-- third_party # 第三方依赖
|-- build.sh # 构建脚本
|-- CMakelists.txt # 工程构建文件
|-- .clang-format # 代码格式化配置文件
|-- .config # 工程配置文件,包含一些工程配置开关
模型服务性能提升
接入框架的服务从并发性和接口优化等层面的优化,性能提升比较明显。
例如接入的nlp三俗服务rt对比:
接入后rt更趋于稳定:
资源节省
由于服务性能以及吞吐量提升明显,所以对于资源的节省帮助比较明显。例如一个cv视频特征服务所需资源从15个减少为2个即可满足服务。
模型服务稳定性提升
以往的服务由于代码质量参差不齐以及PyTorch自带的内存泄露问题导致有很多服务重启的问题,GRPS通过统一的框架解决内存泄露问题,以及最小化用户自定义开发使得服务稳定性更有保障。很多服务接入GRPS后重启问题基本没有再出现。
与Triton
早期有调研过triton引入到业务,但是有一些特性不太能直接满足整体的需求,例如
- 接口层输入输出格式需要更加的自由,二进制文件以及任意字符串的格式,甚至接口层的输入输出需要可以支持完全自定义以便早期迁移大量存量的模型服务。
- 另外需要引入整体的显存和服务限制的功能以解决在共享GPU场景大量服务存在的高显存低使用率的共性问题。
- GRPS花了不少心思与做了不少工作在方便于用户进行自定义拓展上,包含前后处理以及模型推理部分都可以进行自定义拓展,支持python与C++双语言,使得自定义拓展更简单。我们内部的服务其实绝大部分是以自定义拓展方式进行了引入。
- 可拓展的监控系统的引入以便用户在一个地方能够了解整体服务的指标,包含qps、latency、GPU与CPU的内存占用和使用率等。
- 标准化服务Docker构建与部署的CICD流程。
Triton支持的功能很多很强大,我们一些地方进行了借鉴,早期GRPS框架比较简单,随着业务的引入加入了更多的功能。当然LLM方面Triton做的比较完善我们内部的LLM服务目前也是引入Triton+Trtllm进行部署。后续也在计划考虑trtllm以插件的方式引入到GRPS框架。
TODO
框架在持续开发中,计划在未来版本支持:
- 支持更多的推理后端,例如onnx-runtime、tensorrt-llm等。
- 支持更多batching算法,例如continuous batching。
- 支持分布式组装服务,由多个推理后端组装成完整服务。
- 模型推理性能分析工具。
(git上点一个star就是最好的支持,感谢^ ^)