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
    `-- library

17 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.cc

8 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.cc

grpc所用到的幾十個api

client/src/c++/library/http_client.cc

http請求所用到的幾十個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容器內安裝conda、cmake、rapidjson和 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_STRING
    dims: [ -1 ]
  },
  {
    name: "BERT_WEIGHT"
    data_type: TYPE_FP32
    dims: [ -1 ]
  }
]
output {
    name: "SCORE"
    data_type: TYPE_FP32
    dims: [ 1 ]
  }

管道的輸入是可變長度的,因此使用 -1 作為尺寸參數。輸出是一個單浮點數。

要創建通過不同模型的管道和數據流,請包括ensemble_scheduling部分。第一個模型被稱為preprocess,它將摘錄作為輸入,並輸出 BERT 令牌標識符和注意力掩碼以及樹特徵。調度的第一步顯示在模型配置的以下部分中:

ensemble_scheduling {
  step [
    {
      model_name: "preprocess"
      model_version: 1
      input_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: 1
      input_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: 1
      input_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_STRING
    dims: [ 1 ]
  }
]
output [
  {
    name: "CLASSIFICATION"
    data_type: TYPE_FP32
    dims: [ 1000 ]
  },
  {
    name: "SEGMENTATION"
    data_type: TYPE_FP32
    dims: [ 3, 224, 224 ]
  }
]
ensemble_scheduling {
  step [
    {
      model_name: "image_preprocess_model"
      model_version: -1
      input_map {
        key: "RAW_IMAGE"
        value: "IMAGE"
      }
      output_map {
        key: "PREPROCESSED_OUTPUT"
        value: "preprocessed_image"
      }
    },
    {
      model_name: "classification_model"
      model_version: -1
      input_map {
        key: "FORMATTED_IMAGE"
        value: "preprocessed_image"
      }
      output_map {
        key: "CLASSIFICATION_OUTPUT"
        value: "CLASSIFICATION"
      }
    },
    {
      model_name: "segmentation_model"
      model_version: -1
      input_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)