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
|
這個例子通過 具體機制就是:在空閒時間也發送 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.cc 和 yolov5s.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.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 上輸出張量,請使用toDlpack和from_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-large和cuml的輸入:
{
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.cc 和 yolov5s.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)