当前位置: 首页 > news >正文

Triton Inference Server 架构与前后处理方案梳理

Triton Inference Server 架构与前后处理方案梳理

文章目录

  • Triton Inference Server 架构与前后处理方案梳理
  • 0 引言
  • 1 client方案--自己在client端增加前处理和后处理
    • 1.1 client的README总结
    • 1.2 client模块解析
      • 1.2.1 整体目录结构
      • 1.2.2 client/src/c++/library 文件夹
      • 1.2.3 client/src/c++/examples 文件夹
      • 1.2.5 client/src/c++/analyzer 文件夹
    • 1.3 结论--怎么在client端做图片前处理和模型后处理
  • 2 ensemble model--一个模型的输出馈送到另一个模型的场景
    • 2.1 使用集成模型在 NVIDIA Triton 推理服务器上为 ML 模型管道提供服务 - NVIDIA 技术博客
      • 2.1.1 自定义 Python 后端存根
      • 2.1.2 自定义执行环境
      • 2.1.3 使用 Python 后端存根和自定义执行环境
      • 2.1.4 用于预处理和后处理的 model.py 文件的结构
      • 2.1.4 在 GPU 上执行 NVIDIA Triton 中的整个管道
    • 2.2 [model_analyzer/docs/ensemble_quick_start.md at main · triton-inference-server/model_analyzer](https://github.com/triton-inference-server/model_analyzer/blob/main/docs/ensemble_quick_start.md)
    • 2.3 [Bobo-y/triton_ensemble_model_demo: triton server ensemble model demo](https://github.com/Bobo-y/triton_ensemble_model_demo)
    • 2.4 [https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md#ensemble-models](https://github.com/triton-inference-server/model_analyzer/blob/main/docs/ensemble_quick_start.md)
    • 2.5 结论--怎么用ensemble models做图片前处理和模型后处理
  • 3 Business Logic Scripting (BLS)--管道包含循环、条件或其他自定义逻辑
    • 3.1 https://github.com/triton-inference-server/server/blob/main/docs/user_guide/bls.md
    • 3.2 https://github.com/triton-inference-server/backend/tree/main/examples/backends/bls
    • 3.3 [Business Logic Scripting (BLS)](https://github.com/triton-inference-server/python_backend#business-logic-scripting)
    • 3.4 结论--怎么用BLS做图片前处理和模型后处理
  • 4 结论--前处理/后处理的几种方案对比
    • 4.1 client方案的结论
    • 4.2 ensemble models方案的结论
    • 4.3 BLS方案的结论
    • 4.4 最终结论--待讨论
  • 5 参考文献

0 引言

本文原本计划是针对前处理和后处理方案进行整理,但在梳理过程中,有意识的对 Triton Client、Ensemble Model、Business Logic Scripting 等模块 细节做了尽可能多的了解,希望通过这次调研,能了解更多的 Triton 相关知识。

1 client方案–自己在client端增加前处理和后处理

参考文献主要是 https://github.com/triton-inference-server/client 的readme和代码。

1.1 client的README总结

首先把readme整体看一遍,这是最笨但也是最快整体了解client的方法,而且readme一般都会包含所有的比较有价值的点

  • client里面包含了很多库和使用demo;

  • 支持使用系统共享内存或者cuda 共享内存给服务端发送输入数据以及从服务端接收输出数据;

  • 构建方式可以下载release包,也可以用docker镜像,也可以使用cmake从源码构建;

  • 在docker容器中使用cuda共享内存的时候,需要增加–pid host;

  • triton server 支持 ensemble 模型,也就是支持把多个后端模块串成 pipeline,比如第一个用 DALI 后端做前处理,第二个用算法模型做推理,client 直接发原始图像就能拿到推理结果。

通过readme可以看到其实是支持在server端做前处理和后处理的,接下来的章节还是先继续分析下client模块的东西,然后后面会分析下这个ensemble以及DALI的具体内容,然后再最终决定前后处理是在client还是在server去做。

1.2 client模块解析

1.2.1 整体目录结构

先列一下目录结构

root@login:/volume/triton_client_code_2.37.0/client/src# tree -L 2
.
|-- c++
|   |-- CMakeLists.txt
|   |-- examples
|   |-- library
|   |-- perf_analyzer
|   `-- tests
|-- grpc_generated
|   |-- go
|   |-- java
|   `-- javascript
|-- java
|   |-- CMakeLists.txt
|   |-- README.md
|   |-- pom.xml
|   |-- src
|   `-- target
|-- java-api-bindings
|   `-- scripts
`-- python|-- CMakeLists.txt|-- examples`-- library17 directories, 5 files

然后继续列一下C++里面的结构

root@login:/volume/triton_client_code_2.37.0/client/src# cd c++
root@login:/volume/triton_client_code_2.37.0/client/src/c++# tree -L 2
.
|-- CMakeLists.txt
|-- examples
|   |-- CMakeLists.txt
|   |-- ensemble_image_client.cc
|   |-- image_client.cc
|   |-- resnet50.cc
|   |-- resnet50.cc.bak
|   |-- reuse_infer_objects_client.cc
|   |-- simple_grpc_async_infer_client.cc
|   |-- simple_grpc_cudashm_client.cc
|   |-- simple_grpc_custom_args_client.cc
|   |-- simple_grpc_custom_repeat.cc
|   |-- simple_grpc_health_metadata.cc
|   |-- simple_grpc_infer_client.cc
|   |-- simple_grpc_keepalive_client.cc
|   |-- simple_grpc_model_control.cc
|   |-- simple_grpc_sequence_stream_infer_client.cc
|   |-- simple_grpc_sequence_sync_infer_client.cc
|   |-- simple_grpc_shm_client.cc
|   |-- simple_grpc_string_infer_client.cc
|   |-- simple_http_async_infer_client.cc
|   |-- simple_http_cudashm_client.cc
|   |-- simple_http_health_metadata.cc
|   |-- simple_http_infer_client.cc
|   |-- simple_http_model_control.cc
|   |-- simple_http_sequence_sync_infer_client.cc
|   |-- simple_http_shm_client.cc
|   |-- simple_http_string_infer_client.cc
|   |-- yolov5s.cc
|   |-- yolov7-tiny.cc
|   `-- yolov7-tiny.cc.bak
|-- library
|   |-- CMakeLists.txt
|   |-- build
|   |-- cencode.c
|   |-- cencode.h
|   |-- cmake
|   |-- common.cc
|   |-- common.h
|   |-- grpc_client.cc
|   |-- grpc_client.h
|   |-- http_client.cc
|   |-- http_client.h
|   |-- ipc.h
|   |-- json_utils.cc
|   |-- json_utils.h
|   |-- libgrpcclient.ldscript
|   |-- libhttpclient.ldscript
|   |-- shm_utils.cc
|   `-- shm_utils.h
|-- perf_analyzer
|   |-- CMakeLists.txt
|   |-- README.md
|   |-- base_queue_ctx_id_tracker.h
|   |-- client_backend
|   |-- command_line_parser.cc
|   |-- command_line_parser.h
|   |-- concurrency_ctx_id_tracker.h
|   |-- concurrency_manager.cc
|   |-- concurrency_manager.h
|   |-- concurrency_worker.cc
|   |-- concurrency_worker.h
|   |-- constants.h
|   |-- ctx_id_tracker_factory.h
|   |-- custom_load_manager.cc
|   |-- custom_load_manager.h
|   |-- data_loader.cc
|   |-- data_loader.h
|   |-- docs
|   |-- doctest.h
|   |-- fifo_ctx_id_tracker.h
|   |-- ictx_id_tracker.h
|   |-- idle_timer.h
|   |-- iinfer_data_manager.h
|   |-- infer_context.cc
|   |-- infer_context.h
|   |-- infer_data.h
|   |-- infer_data_manager.cc
|   |-- infer_data_manager.h
|   |-- infer_data_manager_base.cc
|   |-- infer_data_manager_base.h
|   |-- infer_data_manager_factory.h
|   |-- infer_data_manager_shm.cc
|   |-- infer_data_manager_shm.h
|   |-- inference_profiler.cc
|   |-- inference_profiler.h
|   |-- ischeduler.h
|   |-- iworker.h
|   |-- load_manager.cc
|   |-- load_manager.h
|   |-- load_worker.cc
|   |-- load_worker.h
|   |-- main.cc
|   |-- metrics.h
|   |-- metrics_manager.cc
|   |-- metrics_manager.h
|   |-- mock_concurrency_worker.h
|   |-- mock_data_loader.h
|   |-- mock_infer_context.h
|   |-- mock_infer_data_manager.h
|   |-- mock_inference_profiler.h
|   |-- mock_model_parser.h
|   |-- mock_request_rate_worker.h
|   |-- mock_sequence_manager.h
|   |-- model_parser.cc
|   |-- model_parser.h
|   |-- mpi_utils.cc
|   |-- mpi_utils.h
|   |-- perf_analyzer.cc
|   |-- perf_analyzer.h
|   |-- perf_analyzer_exception.h
|   |-- perf_analyzer_unit_tests.cc
|   |-- perf_utils.cc
|   |-- perf_utils.h
|   |-- rand_ctx_id_tracker.h
|   |-- rate_schedule.h
|   |-- report_writer.cc
|   |-- report_writer.h
|   |-- request_rate_manager.cc
|   |-- request_rate_manager.h
|   |-- request_rate_worker.cc
|   |-- request_rate_worker.h
|   |-- sequence_manager.cc
|   |-- sequence_manager.h
|   |-- sequence_status.h
|   |-- test_command_line_parser.cc
|   |-- test_concurrency_manager.cc
|   |-- test_ctx_id_tracker.cc
|   |-- test_custom_load_manager.cc
|   |-- test_idle_timer.cc
|   |-- test_infer_context.cc
|   |-- test_inference_profiler.cc
|   |-- test_load_manager.cc
|   |-- test_load_manager_base.h
|   |-- test_metrics_manager.cc
|   |-- test_model_parser.cc
|   |-- test_perf_utils.cc
|   |-- test_report_writer.cc
|   |-- test_request_rate_manager.cc
|   |-- test_sequence_manager.cc
|   `-- test_utils.h
`-- tests|-- CMakeLists.txt|-- cc_client_test.cc|-- client_timeout_test.cc`-- memory_leak_test.cc8 directories, 138 files

所以,本质上其实client是个SDK库,只不过它里面还给加了一些调用的demo,然后里面还有个性能测试的文件夹。

1.2.2 client/src/c++/library 文件夹

这里面其实就是库文件,里面就定义了各种结构体、各种类、各种函数接口。

文件名主要内容
client/src/c++/library/cencode.c这就是个做base64编码的,而且看着像是直接抄的http://sourceforge.net/projects/libb64 。
client/src/c++/library/common.cc这里面就是比如定义了发送推理请求时用到的类,还有一些描述结构体,
还有接收服务端返回数据时要用到的类和结构体,以及这些类里面的那些函数成员的定义
client/src/c++/library/grpc_client.ccgrpc所用到的几十个api
client/src/c++/library/http_client.cchttp请求所用到的几十个api
client/src/c++/library/json_utils.cc里面就一个函数ParseJson,内部实际是用的rapidjson
client/src/c++/library/shm_utils.cc共享内存的相关接口,但是只是对shm_open mmap这种接口的简单封装,
并没有设计内存池以及共享内存管理相关的类,需要自己实现相应的内存池或者管理策略。

1.2.3 client/src/c++/examples 文件夹

我把client/src/c++/examples/下面的这些xxxxx.cc文件全都过了一遍,大体上了解了每个cc文件做的东西。

文件名主要内容
client/src/c++/examples/image_client.cc
client/src/c++/examples/resnet50.cc
client/src/c++/examples/yolov5s.cc
这就是给server发送图片请求的例子,其中resnet50.cc和yolov5s.cc是模仿image_client.cc写的,
图片的前处理和模型的后处理都是在这个cc文件中实现的。
client/src/c++/examples/ensemble_image_client.cc这个例子就是直接将jpg图片读取成char形式,不解码,然后发给server端,由server端去做图片的解码、cvcolour、resize并做算法推理,这就是ensemble,就是将server的两个后端串联起来,一个后端做前处理,一个后端做推理。
client/src/c++/examples/reuse_infer_objects_client.cc这个是关于重复利用同一个input对象发送三次推理请求的,
里面也有关于共享内存的使用方法。
client/src/c++/examples/simple_grpc_async_infer_client.cc这里面就是异步请求的例子,然后里面就是用了回调函数以及条件变量 互斥锁,其他的也没什么东西。
client/src/c++/examples/simple_grpc_cudashm_client.cc利用 CUDA设备显存作为共享内存作为进程间通信的共享内存,然后客户端和服务端直接共享内存通信。
client/src/c++/examples/simple_grpc_custom_args_client.cc这个看着就是个最简单的怎么设置grpc参数的demo。
client/src/c++/examples/simple_grpc_custom_repeat.cc这个就是模拟一次发送一个stream或者多帧数据的情况,然后也是用回调函数以及条件变量 互斥锁,等待所有帧数据都返回后被notify。
client/src/c++/examples/simple_grpc_health_metadata.cc这个是检查服务器是否健康以及获取服务器元数据的,通俗点说就是比如用接口查看服务是否存在、模型是否准备好、获取模型配置等一些接口。
client/src/c++/examples/simple_grpc_infer_client.cc最普通的推理,没用共享内存,没用异步,没repeat。
client/src/c++/examples/simple_grpc_keepalive_client.cc这个例子通过 keepalive_options 参数确保 client 和 server 之间保持长连接(KeepAlive),
具体机制就是:在空闲时间也发送 ping 请求帧,告诉 server“客户端还活着”,从而防止连接被关闭或意外断开。
client/src/c++/examples/simple_grpc_model_control.cc就只是演示了加载模型、卸载模型、检查模型是否ready等几个接口。
client/src/c++/examples/simple_grpc_sequence_stream_infer_client.cc序列推理,也就是说服务端的模型是个状态模型(在这个例子中这个模型是累加模型,也就是每次客户端发送的请求数据都跟之前的历史数据做累加和),能够保存这个模型在前后的一些状态,而客户端的这个所谓的sequence_id就是指哪个序列或者说不同用户,比如用户A 用户B都有各自的序列请求,那么服务端需要根据不同的ID去维护不同用户的状态信息。
这里的sequence_id不能理解成是帧数据的ID,而是要理解成不同用户的ID,比如用户A发送了20帧数据,用户B发送了20个帧数据,那么服务端针对用户A B内部比如说用map或vector维护两个数据结构,一个保存用户A的这20帧数据的计算结果,另一个保存用户B的这20帧数据的计算结果。
client/src/c++/examples/simple_grpc_sequence_sync_infer_client.cc跟client/src/c++/examples/simple_grpc_sequence_stream_infer_client.cc类似,只不过这个是同步阻塞调用方式,而client/src/c++/examples/simple_grpc_sequence_stream_infer_client.cc是用了回调函数+条件变量+互斥锁的异步调用。
client/src/c++/examples/simple_grpc_shm_client.cc这个就是演示了client和server之间的共享内存通信,实现输入输出和 Triton Server 的 零拷贝
client/src/c++/examples/simple_grpc_string_infer_client.cc发送字符串进行推理的示例
simple_http_async_infer_client.cc
simple_http_cudashm_client.cc
simple_http_health_metadata.cc
simple_http_infer_client.cc
simple_http_model_control.cc
simple_http_sequence_sync_infer_client.cc
simple_http_shm_client.cc
simple_http_string_infer_client.cc
这个看文件名字感觉跟前面分析的grpc开头的文件是一样的,只不过这里面使用的是http,就不再挨着看了。

1.2.5 client/src/c++/analyzer 文件夹

PerfAnalyzer 其实就是一个性能评测工具,能够分析 Triton 推理服务端的吞吐量、延迟、服务器端队列/执行时间等各种性能指标。

这个文件夹从名字就能看出来是性能分析的,然后我看了下里面的那一堆文件,他和前面的example文件夹不一样,examples文件夹里面是每个.cc文件最终都会给编译成一个可执行程序,因为每个.cc文件里面都有main函数,但是在analyzer文件夹里面,只有main.cc里面是有main函数的,其他的那些文件其实相当于是analyzer的库函数。

而main.cc里面的main函数就几行

int
main(int argc, char* argv[])
{try {triton::perfanalyzer::CLParser clp;pa::PAParamsPtr params = clp.Parse(argc, argv);PerfAnalyzer analyzer(params);analyzer.Run();}catch (pa::PerfAnalyzerException& e) {return e.GetError();}return 0;
}

然后就是通过这里的params可以进行各种功能分析,然后这些参数怎么给,详细介绍在client/src/c++/perf_analyzer/docs/cli.md里面。

也可以运行root@login:/volume/triton_client_code_2.37.0/client/install/bin# ./perf_analyzer查看。

1.3 结论–怎么在client端做图片前处理和模型后处理

resnet50.ccyolov5s.cc 示例中,图片的前处理和模型的后处理都是在这个cc文件中实现的,imread–cvtcolor-resize,然后把前处理之后的数据发送给服务端,然后服务端返回结果之后再做nms或softmax.

  • 针对前处理和后处理慢的问题

    • 目前的demo中前处理和后处理都是直接在cpu上去做的,时间比较长。

    • 如果client也是部署在带DCU的服务器上,那么这里的图片前处理可以换成用海光硬件接口去加速,然后后处理可以用HIP/CUDA编写kernel去做,

    • 如果client是部署在一些嵌入式前端设备上(IPC相机、边缘设备),只有服务端是部署在带DCU的服务器上面,那么该方法不可行。

  • 针对网络发送耗时的问题

    • 在client做前处理和后处理,处理后的图像通常是浮点格式(如 FP32/FP16 Tensor),数据量远大于原始 JPEG/PNG 图像;
    • 在 HTTP / gRPC 协议下传输体积大的张量,会明显增加带宽负担,进而拉高整体推理延迟;
    • 针对发送数据量大的问题,可以采用共享内存的方式去做,但是目前demo中只是提供了像shm_open mmap这种简单函数,如果用共享内存,那么需要开发内存池相关代码作为管理员的角色。

2 ensemble model–一个模型的输出馈送到另一个模型的场景

主要参考文献是

使用集成模型在 NVIDIA Triton 推理服务器上为 ML 模型管道提供服务 - NVIDIA 技术博客

model_analyzer/docs/ensemble_quick_start.md at main · triton-inference-server/model_analyzer

Bobo-y/triton_ensemble_model_demo: triton server ensemble model demo

https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md#ensemble-models

core/src/ensemble_scheduler at main · triton-inference-server/core

2.1 使用集成模型在 NVIDIA Triton 推理服务器上为 ML 模型管道提供服务 - NVIDIA 技术博客

2.1.1 自定义 Python 后端存根

Python 后端使用存根进程将model.py文件连接到 NVIDIA Triton C++核心。 Python 后端可以使用安装在当前[ZGK8环境(虚拟环境或Conda环境)或全局 Python 环境中的库。

请注意,这假设用于编译后端存根的 Python 版本与用于安装依赖项的版本相同。写入时使用的 NVIDIA Triton 容器中的默认 Python 版本为 3.8 。如果需要运行预处理和后处理的 Python 版本与 NVIDIA Triton 容器中的版本不匹配,则需要编译 custom Python backend stub 。

要创建自定义 Python 后端存根,请在 NVIDIA conda容器内安装condacmakerapidjson和 Triton 。接下来,创建一个 Conda 虚拟环境(请参见 documentation ),并使用以下命令激活它:

conda create -n custom_env python=<python-version>
conda init bash
bash
conda activate custom_env

<python-version>替换为感兴趣的版本,例如 3.9 。然后克隆 Python 后端回购,并使用以下代码编译 Python 后端存根:

git clone https://github.com/triton-inference-server/python_backend -b r<xx.yy>
cd python_backend
mkdir build && cd build
cmake -DTRITON_ENABLE_GPU=ON -DCMAKE_INSTALL_PREFIX:PATH=`pwd`/install ..
make triton-python-backend-stub

请注意,<xx.yy>必须替换为 NVIDIA Triton 容器版本。运行上面的命令将创建名为triton-python-backend-stub的存根文件。这个 Python 后端存根现在可以用来加载安装有匹配版本[ZGK8的库。

2.1.2 自定义执行环境

如果您想为不同的 Python 型号使用不同的 Python 环境,您需要创建一个 custom execution environment 。要为 Python 模型创建自定义执行环境,第一步是在已经激活的 Conda 环境中安装任何必要的依赖项。然后运行conda-pack将我们的自定义执行环境打包为 tar 文件。这将创建一个名为custom_env.tar.gz的文件。

在撰写本文时, NVIDIA Triton 仅支持conda-pack用于捕获执行环境。请注意,当在 NVIDIA Triton Docker 容器中工作时,容器中包含的包也会在conda-pack创建的执行环境中捕获。

2.1.3 使用 Python 后端存根和自定义执行环境

在创建 Python 后端存根和自定义执行环境后,将两个生成的文件从容器复制到模型存储库所在的本地系统。在本地系统中,将存根文件复制到每个需要使用存根的 Python 模型(即预处理和后处理模型)的模型目录中。对于这些模型,目录结构如下:

model_repository├── postprocess│   ├── 1│   │   └── model.py│   ├── config.pbtxt│   └── triton_python_backend_stub└── preprocess├── 1│   └── model.py├── config.pbtxt└── triton_python_backend_stub

对于预处理和后处理模型,还需要在配置文件中提供自定义执行环境的 tar 文件的路径。例如,预处理模型的配置文件将包括以下代码:

name: "preprocess"
backend: "python"...
parameters: {key: "EXECUTION_ENV_PATH",value: {string_value: "path/to/custom_env.tar.gz"}
}

要使此步骤生效,请将custom_env.tar.gz存储在 NVIDIA Triton 推理服务器容器可以访问的路径中。

2.1.4 用于预处理和后处理的 model.py 文件的结构

每个 Python 模型都需要遵循 documentation 中描述的特定结构。在model.py文件中定义以下三个功能:

1. **initialize**(可选,在加载模型时运行):用于在推理之前加载必要的组件,以减少每个客户端请求的开销。特别是对于预处理,加载 cuDF tokenizer ,它将用于标记基于 BERT 的模型的摘录。还加载用于随机森林特征生成的停止词列表,作为对基于树的模型的输入的一部分。

2 .execute(必需,请求推理时运行):接收推理请求,修改输入,并返回推理响应。由于preprocess是推理的入口点,如果要对 GPU 执行推理,则需要将输入移动到[Z1K1’。

通过创建cudf.Series的实例将摘录输入张量移动到 GPU ,然后利用initialize中加载的 cuDF 标记器在[Z1K1’上标记整批摘录。

类似地,使用字符串操作生成基于树的特征,然后使用在 GPU 上操作的CuPY数组对特征进行归一化。要在 GPU 上输出张量,请使用toDlpackfrom_dlpack(参见 documentation )将张量封装到推理响应中。

最后,为了保持张量在 GPU 上,并避免在集合中的步骤之间复制到 CPU ,请将以下内容添加到每个模型的配置文件中:

parameters: {key: "FORCE_CPU_ONLY_INPUT_TENSORS"value: {string_value:"no"}
}

postprocess的输入分数已经在 GPU 上了,所以只需将分数与CuPY数组再次组合,并输出最终的管道分数。对于在 CPU 上进行后处理的情况,在预处理步骤中将ML模型的输出移动到[Z1K2’。

3. **finalize**(可选,在卸载模型时运行):使您能够在从 NVIDIA Triton 服务器卸载模型之前完成任何必要的清理。

2.1.4 在 GPU 上执行 NVIDIA Triton 中的整个管道

要让 NVIDIA Triton 运行执行管道,需要创建一个名为ensemble_all的 ensemble model 。该模型与任何其他模型具有相同的模型目录结构,只是它不存储任何模型,并且只由一个配置文件组成。集成模型的目录如下所示:

├── ensemble_all│   ├── 1│   └── config.pbtxt

在配置文件中,首先使用以下脚本指定集成模型名称和后端:

name: "ensemble_all"
backend: "ensemble"

这里我在代码中找了下,其实没有个ensemble的backend,应该是core/src/ensemble_scheduler at main · triton-inference-server/core这块代码支持这个ensemble配置的。

接下来,定义集合的端点,即集合模型的输入和输出:

接下来,定义集合的端点,即集合模型的输入和输出:

input [{name: "excerpt"data_type: TYPE_STRINGdims: [ -1 ]},{name: "BERT_WEIGHT"data_type: TYPE_FP32dims: [ -1 ]}
]
output {name: "SCORE"data_type: TYPE_FP32dims: [ 1 ]}

管道的输入是可变长度的,因此使用 -1 作为尺寸参数。输出是一个单浮点数。

要创建通过不同模型的管道和数据流,请包括ensemble_scheduling部分。第一个模型被称为preprocess,它将摘录作为输入,并输出 BERT 令牌标识符和注意力掩码以及树特征。调度的第一步显示在模型配置的以下部分中:

ensemble_scheduling {step [{model_name: "preprocess"model_version: 1input_map {key: "INPUT0"value: "excerpt"}output_map {key: "BERT_IDS"value: "bert_input_ids",}output_map {key: "BERT_AM"value: "bert_attention_masks",}output_map {key: "TREE_FEATS"value: "tree_feats",}},

step部分中的每个元素都指定了要使用的模型,以及如何将模型的输入和输出映射到集合调度器识别的张量名称。然后使用这些张量名称来识别各个输入和输出。

例如,step中的第一个元素指定应使用preprocess模型的版本一,其输入"INPUT0"的内容由"excerpt"张量提供,其输出"BERT_IDS"的内容将映射到"bert_input_ids"张量以供以后使用。类似的推理适用于preprocess的其他两个输出。

继续在配置文件中添加步骤以指定整个管道,将preprocess的输出传递到bert-largecuml的输入:

{model_name: "bert-large"model_version: 1input_map {key: "INPUT__0"value: "bert_input_ids"}input_map {key: "INPUT__1"value: "bert_attention_masks"}output_map {key: "OUTPUT__0"value: "bert_large_score"}},

最后,通过在配置文件中添加以下行,将这些分数中的每一个传递到后处理模型,以计算分数的平均值并提供单个输出分数,如下所示:

{model_name: "postprocess"model_version: 1input_map {key: "BERT_WEIGHT_INPUT"value: "BERT_WEIGHT"}input_map {key: "BERT_LARGE_SCORE"value: "bert_large_score"}input_map {key: "CUML_SCORE"value: "cuml_score"}output_map {key: "OUTPUT0"value: "SCORE"}}}
]

在集成模型的配置文件中调度整个管道的简单性证明了使用 NVIDIA Triton 进行端到端推理的灵活性。要添加另一个模型或添加另一数据处理步骤,请编辑集成模型的配置文件并更新相应的模型目录。

请注意,集成配置文件中定义的max_batch_size必须小于或等于每个模型中定义的max_batch_size。整个模型目录,包括集成模型,如下所示:

├── bert-large
│   ├── 1
│   │   └── model.pt
│   └── config.pbtxt
├── cuml
│   ├── 1
│   │   └── checkpoint.tl
│   └── config.pbtxt
├── ensemble_all
│   ├── 1
│   │   └── empty
│   └── config.pbtxt
├── postprocess
│   ├── 1
│   │   ├── model.py
│   └── config.pbtxt
└── preprocess├── 1│   ├── model.py└── config.pbtxt

要告诉 NVIDIA Triton 执行 GPU 上的所有模型,请在每个模型的配置文件中包括以下行(集成模型的配置文档中除外):

instance_group[{kind:KIND_GPU}]

在 CPU 上执行 NVIDIA Triton 中的整个管道若要让 NVIDIA Triton 在 CPU 上执行整个管道,请重复在 GPU 上运行管道的所有步骤。在每个配置文件中将instance_group[{kind:KIND_GPU}]替换为以下行:

instance_group[{kind:KIND_CPU}]

2.2 model_analyzer/docs/ensemble_quick_start.md at main · triton-inference-server/model_analyzer

如何使用 NVIDIA Triton 的 Model Analyzer 工具来分析一个简单的 ensemble 模型性能。

这里先记一下,以后用到了就去这个网址找。

2.3 Bobo-y/triton_ensemble_model_demo: triton server ensemble model demo

https://github.com/Bobo-y/triton_ensemble_model_demo/tree/main/models

这就是个pipeline的例子,不过他里面的backend是用python写的,后面可以参考里面的config.pbtxt,具体的C++ backend自己写。

2.4 https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md#ensemble-models

直接翻译一下readme,理解一下

Ensemble 模型

Ensemble 模型表示由一个或多个模型组成的处理流水线,并定义这些模型之间输入输出张量的连接关系。Ensemble 模型的主要用途是封装一个包含多个模型的处理流程,比如“数据预处理 → 推理 → 数据后处理”。使用 ensemble 模型可以避免在多个模型间传输中间张量的开销,并减少发送到 Triton 的请求次数。

Ensemble 模型必须使用 ensemble scheduler(调度器),不管其中的子模型使用了哪种调度方式。对于 ensemble scheduler 来说,ensemble 模型本身并不是一个“实际模型”,而是通过模型配置中的 ModelEnsembling::Step 条目定义了子模型之间的数据流关系。调度器会收集每个步骤的输出张量,并按照配置将其作为输入提供给其他步骤。尽管如此,从外部来看,ensemble 模型仍然被视为一个“单一模型”。

需要注意的是,ensemble 模型会继承其所包含子模型的特性,因此请求头中的元数据必须满足所有子模型的要求。例如,如果其中一个子模型是有状态模型(stateful model),那么对 ensemble 模型的推理请求就必须包含与之相关的状态信息,这些信息会由调度器传递给对应的有状态模型。

然后下面是一个例子

name: "ensemble_model"
platform: "ensemble"
max_batch_size: 1
input [{name: "IMAGE"data_type: TYPE_STRINGdims: [ 1 ]}
]
output [{name: "CLASSIFICATION"data_type: TYPE_FP32dims: [ 1000 ]},{name: "SEGMENTATION"data_type: TYPE_FP32dims: [ 3, 224, 224 ]}
]
ensemble_scheduling {step [{model_name: "image_preprocess_model"model_version: -1input_map {key: "RAW_IMAGE"value: "IMAGE"}output_map {key: "PREPROCESSED_OUTPUT"value: "preprocessed_image"}},{model_name: "classification_model"model_version: -1input_map {key: "FORMATTED_IMAGE"value: "preprocessed_image"}output_map {key: "CLASSIFICATION_OUTPUT"value: "CLASSIFICATION"}},{model_name: "segmentation_model"model_version: -1input_map {key: "FORMATTED_IMAGE"value: "preprocessed_image"}output_map {key: "SEGMENTATION_OUTPUT"value: "SEGMENTATION"}}]
}

2.5 结论–怎么用ensemble models做图片前处理和模型后处理

  • **什么是ensemble models:**首先通俗点说,集成模型本身并不是个模型,也没有模型文件,他是用一个config文件,然后config文件里面相当于把其他已有的模型和backend给衔接起来,一个模型的输出作为另一个模型的输入,然后类似一个管道。

  • 怎么用ensemble models做图片前处理和模型后处理:需要我们编写一个preprocess的backend,编写一个postprocess的backend,然后用ensemble models的形式把前处理、推理、后处理给串起来。

  • 不同模型需要实现多个backend:比如不同的检测模型,他们的后处理是不一样的,那么需要写不同的backend做后处理,还有比如分类模型和检测模型,他们的图片前处理肯定也不一样,也需要实现不同的前处理的backend。

  • **支持动态替换:**只需修改 ensemble 的配置即可更换模型或处理逻辑,无需修改客户端代码。

3 Business Logic Scripting (BLS)–管道包含循环、条件或其他自定义逻辑

我在扒拉GitHub的时候又发现了一个BLS backend, https://github.com/triton-inference-server/backend/tree/main/examples/backends/bls ,不知道这个是干什么的,然后把这个看了下,发现这也可以为一种方案,后来又发现了一个地方:Business Logic Scripting (BLS) 。

3.1 https://github.com/triton-inference-server/server/blob/main/docs/user_guide/bls.md

这里面是一些bls的介绍,但只是python 的,没有C++的。

3.2 https://github.com/triton-inference-server/backend/tree/main/examples/backends/bls

这是个bls backend的C++demo,但是这个demo不支持batch,也只是在cpu上,后期如果真正使用,那么需要我们自己扩展这个demo。

3.3 Business Logic Scripting (BLS)

看了下,这里面的内容跟https://github.com/triton-inference-server/server/blob/main/docs/user_guide/bls.md其实是一样的基本上。

3.4 结论–怎么用BLS做图片前处理和模型后处理

  • 为什么要用BLS:因为ensemble只适合那种串行的pipeline,当我们在加载不同模型的时候如果需要一些条件判断、循环或者复杂逻辑,那么ensemble就不适合,所以就要用BLS。
  • 什么是BLS:BLS 是在 Triton 自定义后端(如 Python Backend 或 C++ Backend)中,通过编写自定义逻辑,在模型的 execute()(或 ModelInstanceExecute)函数中显式地发起对其他模型的推理请求,构建自己的业务流程。
  • BLS实现方式1:写一个前处理backend,后处理backend, 推理backend(用现有的),这个bls的backend他的推理不做具体单个模型的推理,而是在bls的execute接口内部调用其他三个backend的接口做前处理、推理、后处理、复杂逻辑判断。
  • BLS实现方式2:不写前处理和后处理的backend,直接在bls的execute函数内部完完成前处理,然后调用推理接口,然后做后处理,只不过这样耦合性太高了,可复用度比较差。
  • 其实bls其实也可以理解成类似ensemble的config.pbtxt的角色,只不过相当于triton内部已经支持了ensemble,所以我们只写一个config.pbtxt那么就可以用ensemble就行了,而bls我们是自己实现,里面是我们自己想要的复杂逻辑。

4 结论–前处理/后处理的几种方案对比

先把前面的几种方案的结论复制到这里来。

4.1 client方案的结论

resnet50.ccyolov5s.cc 示例中,图片的前处理和模型的后处理都是在这个cc文件中实现的,imread–cvtcolor-resize,然后把前处理之后的数据发送给服务端,然后服务端返回结果之后再做nms或softmax.

  • 针对前处理和后处理慢的问题

    • 目前的demo中前处理和后处理都是直接在cpu上去做的,时间比较长。

    • 如果client也是部署在带DCU的服务器上,那么这里的图片前处理可以换成用海光硬件接口去加速,然后后处理可以用HIP/CUDA编写kernel去做,

    • 如果client是部署在一些嵌入式前端设备上(IPC相机、边缘设备),只有服务端是部署在带DCU的服务器上面,那么该方法不可行。

  • 针对网络发送耗时的问题

    • 在client做前处理和后处理,处理后的图像通常是浮点格式(如 FP32/FP16 Tensor),数据量远大于原始 JPEG/PNG 图像;
    • 在 HTTP / gRPC 协议下传输体积大的张量,会明显增加带宽负担,进而拉高整体推理延迟;
    • 针对发送数据量大的问题,可以采用共享内存的方式去做,但是目前demo中只是提供了像shm_open mmap这种简单函数,如果用共享内存,那么需要开发内存池相关代码作为管理员的角色。

4.2 ensemble models方案的结论

  • **什么是ensemble models:**首先通俗点说,集成模型本身并不是个模型,也没有模型文件,他是用一个config文件,然后config文件里面相当于把其他已有的模型和backend给衔接起来,一个模型的输出作为另一个模型的输入,然后类似一个管道。

  • 怎么用ensemble models做图片前处理和模型后处理:需要我们编写一个preprocess的backend,编写一个postprocess的backend,然后用ensemble models的形式把前处理、推理、后处理给串起来。

  • 不同模型需要实现多个backend:比如不同的检测模型,他们的后处理是不一样的,那么需要写不同的backend做后处理,还有比如分类模型和检测模型,他们的图片前处理肯定也不一样,也需要实现不同的前处理的backend。

  • **支持动态替换:**只需修改 ensemble 的配置即可更换模型或处理逻辑,无需修改客户端代码。

4.3 BLS方案的结论

  • 为什么要用BLS:因为ensemble只适合那种串行的pipeline,当我们在加载不同模型的时候如果需要一些条件判断、循环或者复杂逻辑,那么ensemble就不适合,所以就要用BLS。
  • 什么是BLS:BLS 是在 Triton 自定义后端(如 Python Backend 或 C++ Backend)中,通过编写自定义逻辑,在模型的 execute()(或 ModelInstanceExecute)函数中显式地发起对其他模型的推理请求,构建自己的业务流程。
  • BLS实现方式1:写一个前处理backend,后处理backend, 推理backend(用现有的),这个bls的backend他的推理不做具体单个模型的推理,而是在bls的execute接口内部调用其他三个backend的接口做前处理、推理、后处理、复杂逻辑判断。
  • BLS实现方式2:不写前处理和后处理的backend,直接在bls的execute函数内部完完成前处理,然后调用推理接口,然后做后处理,只不过这样耦合性太高了,可复用度比较差。
  • 其实bls其实也可以理解成类似ensemble的config.pbtxt的角色,只不过相当于triton内部已经支持了ensemble,所以我们只写一个config.pbtxt那么就可以用ensemble就行了,而bls我们是自己实现,里面是我们自己想要的复杂逻辑。

4.4 最终结论–待讨论

初步pipeline的方案

首选 Ensemble 方式实现推理流水线

  • 先开发 Preprocess Backend 和 Postprocess Backend。
  • 通过 Triton 的 Ensemble Model 配置文件,将前处理、推理、后处理串联成完整流程。

BLS(Backend Lifecycle Scheduler)方式实现

  • 同时实现基于 BLS 的多模型推理逻辑,支持更复杂的模型调用流程,满足客户多模型推理需求。

Client 端推理方案实现

  • 实现 Client 端前处理与后处理,支持数据预处理和结果后处理。
  • 优化数据传输,开发共享内存与内存池方案,实现客户端与服务端零拷贝数据传输,提高效率。

后期扩展一:服务端视频解码 Backend

  • 开发视频解码 Backend,支持服务端接收 RTSP 地址做视频解码,完成视频解码、前处理、推理、后处理的完整 Pipeline。

后期扩展二:服务端跟踪 Backend

  • 利用 ByteTrack 算法开发跟踪 Backend,实现检测结果的多目标跟踪,满足客户更复杂的视频分析需求。

其它扩展:充分熟悉server代码

  • 通过前面的开发和调试工作,充分熟悉triton的代码,后期可以对triton进行各种魔改和扩展,以适配更多的应用场景。

5 参考文献

https://github.com/triton-inference-server/client

使用集成模型在 NVIDIA Triton 推理服务器上为 ML 模型管道提供服务 - NVIDIA 技术博客

model_analyzer/docs/ensemble_quick_start.md at main · triton-inference-server/model_analyzer

Bobo-y/triton_ensemble_model_demo: triton server ensemble model demo

https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md#ensemble-models

core/src/ensemble_scheduler at main · triton-inference-server/core

https://github.com/triton-inference-server/server/blob/main/docs/user_guide/bls.md

https://github.com/triton-inference-server/backend/tree/main/examples/backends/bls

Business Logic Scripting (BLS)

http://www.dtcms.com/a/278610.html

相关文章:

  • 打破空间边界!Nas-Cab用模块化设计重构个人存储逻辑
  • JAVA进阶--JVM
  • 设备发出、接收数据帧的工作机制
  • 无人机迫降模式模块运行方式概述!
  • 掉线监测-tezos rpc不能用,改为残疾网页监测
  • .net winfrom 获取上传的Excel文件 单元格的背景色
  • 深入浅出Kafka Producer源码解析:架构设计与编码艺术
  • 创客匠人:创始人 IP 打造的破局点,藏在 “小而精” 的需求里
  • React源码3:update、fiber.updateQueue对象数据结构和updateContainer()中enqueueUpdate()阶段
  • 分布式系统中设计临时节点授权的自动化安全审计
  • postgreSQL的sql语句
  • 时序预测 | Pytorch实现CNN-LSTM-KAN电力负荷时间序列预测模型
  • 2025 春秋杯夏季个人挑战赛 Web
  • lesson13:Python的datetime模块
  • 登录校验与异常处理(web后端笔记第三期)
  • NAT原理与实验指南:网络地址转换技术解析与实践
  • 中国AI应用“三分天下”:国企成主力、中小企偏订阅、C端仍在观望
  • 使用axios向服务器请求信息并渲染页面
  • TCP心跳机制详解
  • 【Linux系统】进程切换 | 进程调度——O(1)调度队列
  • 如何在服务器上运行一个github项目
  • VMware 虚拟机 Ubuntu 无法主机与虚拟机之间复制粘贴的详细解决方案
  • ZLMediaKit流媒体服务器:不用docker -java源码部署Linux问题处理
  • day20 力扣235. 二叉搜索树的最近公共祖先 力扣701.二叉搜索树中的插入操作 力扣450.删除二叉搜索树中的节点
  • 8:从USB摄像头把声音拿出来--ALSA大佬登场!
  • Bash常见条件语句和循环语句
  • rk3588平台USB 3.0 -OAK深度相机适配方法
  • springboot 好处
  • [Nagios Core] 事件调度 | 检查执行 | 插件与进程
  • JAVA 设计模式 适配器